157 Commits
v2.2 ... v2.4

Author SHA1 Message Date
b6e668bc53 send disconnect on server shutdown. 2021-09-11 18:21:28 +02:00
b7950b3650 fix stopsignal for go program. 2021-09-11 18:15:12 +02:00
6ee8bb86fe go mod update. 2021-09-11 17:45:05 +02:00
f16ffba963 remove xserver-xorg-input-void dep. 2021-08-31 18:45:25 +02:00
334f60ac7c upgrade to debian 11, fixes #91. 2021-08-31 18:29:55 +02:00
9be75c20ca typescript catch error any. 2021-08-31 18:25:06 +02:00
81006e02ed node upgrade. 2021-08-31 18:14:50 +02:00
78bf9e276b golang upgrade deps. 2021-08-31 18:13:34 +02:00
ecf59a39bc bump golang version. 2021-08-31 18:13:05 +02:00
def879c586 add empty_displayname to locale. 2021-08-31 18:04:55 +02:00
b808530f0c use async / await. 2021-08-31 17:58:32 +02:00
aae55ea585 edit readme. 2021-08-22 16:14:37 +02:00
89f4e697c2 js lint fix. 2021-08-15 16:43:59 +02:00
523831237d refactor broadcast UI. 2021-08-15 16:41:42 +02:00
313cd55049 add system error. 2021-08-15 16:05:26 +02:00
9e466b08cc catch errors from gst pipeline. 2021-08-15 15:37:27 +02:00
6708ce2caf npm update. 2021-08-15 14:59:06 +02:00
0185178234 go upgrade. 2021-08-15 14:57:39 +02:00
6fcf9ff07e Merge branch 'dev' of github.com:m1k1o/neko into dev 2021-08-15 14:52:43 +02:00
c2fa40d3ff Install without Docker, fixes #87. 2021-08-15 14:51:55 +02:00
d27c329fac How to use RTMP feature, fixes #88. 2021-08-15 14:49:05 +02:00
ab05b3124f Update Chromium extensions (#89)
uBlock Origin updated to v1.37.2
NordVPN updated to v2.31.0

Signed-off-by: Aaron <admin@datahoarder.dev>
2021-08-04 23:34:42 +02:00
4410956eae update README.md 2021-07-26 17:01:05 +02:00
db66cb565c readme: add cap-add SYS_ADMIN to brave. 2021-07-26 16:49:51 +02:00
85d6205823 Lib build fix $te ref (#86) 2021-07-24 18:05:14 +02:00
7cfc8c4795 added brave browser. 2021-07-24 10:35:45 +02:00
c3abbfd577 do not handle msgs on nonexisting id. 2021-07-22 20:58:39 +02:00
46b8c9f42d golang minor changes cleanup. 2021-07-22 20:58:15 +02:00
609b3f0927 Merge branch 'firefox-profile' into dev 2021-07-22 20:45:49 +02:00
bc2fa6cd40 add link to header. 2021-07-22 20:13:58 +02:00
1bf588511a about: use dev branch. 2021-07-22 20:08:38 +02:00
7d94ee603d change version & add fork author. 2021-07-22 20:07:03 +02:00
eb057f1ab2 add description to html. 2021-07-22 20:05:47 +02:00
5606bfa174 change about page to up-to-date. 2021-07-22 20:03:44 +02:00
122fd8b957 custom kick message, fixes #56. 2021-07-17 19:37:50 +02:00
b9b80ed79a show reconnecting notify. #48 2021-07-17 19:18:21 +02:00
2cff2a340f show badge on new messages. 2021-07-17 12:38:12 +02:00
429fc7eb68 fix fast scroll speed on macos. (#85) 2021-07-17 11:56:26 +02:00
ffcca402ef fixed mac os keyboard mapping. (#84) 2021-07-17 11:56:16 +02:00
38bed98741 fix timeout typescript. 2021-07-17 11:30:46 +02:00
45ef058bf4 Update uBlock Origin to v1.36.2 (#80)
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-07-12 13:43:53 +02:00
e5c59faf1a firefox profile directory. 2021-07-06 12:32:04 +02:00
c392cad046 Update uBlock Origin to v1.36.0 (#77)
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-07-05 16:52:51 +02:00
d41580050e switch to latest firefox version. 2021-07-04 20:15:56 +02:00
02a38d8805 change badges in README. 2021-06-29 00:19:19 +02:00
bf428c5154 upgrade dependencies. 2021-06-28 23:54:38 +02:00
86a9effe41 don't kill webrtc on temporary network issues #48. 2021-06-27 22:20:15 +02:00
733c39412b use log panic in webrtc. 2021-06-26 13:45:22 +02:00
7aa7e0eacb custom ipfetch #63. 2021-06-26 13:44:41 +02:00
3a79615a2b ungoogled-chromium fetch latest release. 2021-06-26 13:19:49 +02:00
da246345e1 update readme. 2021-06-26 13:19:18 +02:00
1800d077d8 Build by github actions (#70)
* BUILD_IMAGE from environment vairables has precedence.

* add workflow.

* trigger actions.
2021-06-13 14:13:19 +02:00
8612286c98 Merge pull request #69 from whalehub/update-ublock-origin
Update uBlock Origin to v1.35.2
2021-06-12 20:32:29 +02:00
5c74d304ff Update uBlock Origin to v1.35.2
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-06-12 17:24:11 +02:00
2f3d0d9bbd Merge pull request #68 from whalehub/patch-1 2021-06-11 08:45:31 +02:00
3792be85cc Dockerfile: Update Ungoogled Chromium to v91.0.4472.101
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-06-11 08:15:11 +02:00
0319455e55 #67 Revert "auto unmute on any control attempt #65."
This reverts commit 29359baf65.
2021-06-08 21:32:10 +02:00
57af427c12 update readme. 2021-06-08 13:25:36 +02:00
201197f7d3 add Google Chrome. 2021-06-07 20:00:55 +02:00
eca75dcd07 install xserver-xorg-input-void. 2021-06-07 19:52:22 +02:00
29359baf65 auto unmute on any control attempt #65. 2021-06-07 11:59:06 +02:00
305271916c start unmuted on reconnects #65. 2021-06-07 11:57:44 +02:00
93cb227a1f get ip, no proxy #63. 2021-05-29 21:08:41 +02:00
7e2baf432f add chrome sandbox #64. 2021-05-29 21:01:55 +02:00
d46c9baade update Readme. 2021-05-29 20:58:13 +02:00
8177b23557 fullscreen fallback to video #62. 2021-05-24 23:56:01 +02:00
3dbb265ef3 upgrade server dependencies. 2021-05-23 19:57:41 +02:00
35a092630e logs ignore healthcheck requests. 2021-05-23 19:21:36 +02:00
ce42d81eeb apply debug from env. 2021-05-23 19:18:57 +02:00
e47fa91381 lint fixes. 2021-05-23 18:50:47 +02:00
0acd7f5150 Merge pull request #60 from whalehub/patch-1
Dockerfile: Update Ungoogled Chromium to v90.0.4430.212
2021-05-21 19:09:59 +02:00
43c484ee48 Dockerfile: Update Ungoogled Chromium to v90.0.4430.212
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-05-21 18:57:22 +02:00
df245924cc Merge pull request #58 from RisedSky/RisedSky-patch-1
French i18n
2021-05-14 10:41:15 +02:00
01ba84a417 Update index.ts
French
2021-05-14 04:07:54 +02:00
93d3e811e0 Create fr-fr.ts
French
2021-05-14 04:06:36 +02:00
19c21cadd9 Merge pull request #54 from comradekingu/patch-2
Norwegian Bokmål translation
2021-05-07 11:21:32 +02:00
a8f0ddc468 Merge pull request #53 from comradekingu/patch-1
App language reworked
2021-05-07 11:21:04 +02:00
28dab591a4 nb_NO added 2021-05-07 02:57:01 +02:00
0be72c6507 Norwegian Bokmål translation 2021-05-07 02:55:12 +02:00
fa97f96fd5 App language reworked 2021-05-07 02:47:03 +02:00
b420931055 Merge pull request #51 from whalehub/patch-1
Dockerfile: Update Ungoogled Chromium to v90.0.4430.93
2021-05-01 21:48:08 +02:00
64e673cbd7 Dockerfile: Update Ungoogled Chromium to v90.0.4430.93
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-05-01 21:46:19 +02:00
b875e7be13 Merge pull request #50 from whalehub/dev 2021-04-22 17:18:36 +02:00
501ee120e8 Dockerfile: Update Ungoogled Chromium to v90.0.4430.85
Signed-off-by: Aaron <admin@datahoarder.dev>
2021-04-22 17:12:34 +02:00
6d38ea71b9 Merge pull request #46 from mbattista/multiple-keys
pressing key with multiple pressed keys
2021-04-12 19:47:49 +02:00
f8ba35119e add XkbKeysymToKeycode source link. 2021-04-12 19:43:46 +02:00
4320a2b299 xorg fix error reporting. 2021-04-12 19:42:58 +02:00
b13b1907f4 xorg simplifz names. 2021-04-12 19:41:36 +02:00
1ec8bd34a6 xorg join search + delete to pop. 2021-04-12 19:38:13 +02:00
1307236f86 change head list name. 2021-04-12 19:30:19 +02:00
285d4b630b node -> xkeys_t, moved to .h file. 2021-04-12 19:29:19 +02:00
b169195b69 xorg ulong -> KeySym. 2021-04-12 19:22:59 +02:00
7f226842df loopbreaker and fixes 2021-04-12 14:16:57 +00:00
9386cbb2e2 append -> insert 2021-04-11 12:38:18 +00:00
58cb161bf3 remove additional newline. 2021-04-11 12:26:45 +02:00
b2effce0e7 lint fix. 2021-04-11 12:25:02 +02:00
c54e8327ac revert 'workaround for #45.' 2021-04-11 12:12:06 +02:00
07d111af36 readded list. removed bug. 2021-04-11 08:35:54 +00:00
1038dd109a more cleanup 2021-04-10 23:48:23 +00:00
82062637ae duplicated code 2021-04-10 23:33:58 +00:00
e88521f94e list not really needed 2021-04-10 23:22:37 +00:00
79e3e153bd with comments for if stucts are needed later 2021-04-10 23:17:05 +00:00
be3453c37d pressing key with multiple pressed keys 2021-04-10 11:43:04 +00:00
e5ca4ac184 us mac variant #45. 2021-04-07 22:50:40 +02:00
1a09442f26 workaround for #45. 2021-04-07 22:19:50 +02:00
89dd22727c Merge pull request #44 from mbattista/fix-emojis
fix emojis
2021-04-05 01:11:36 +02:00
d154e1f6bb remove unused code. 2021-04-05 01:08:16 +02:00
9a437b63ff fix emojis 2021-04-04 22:36:47 +00:00
4589f758c6 Merge pull request #43 from mbattista/add-turn-server
allow to add password protected turn server
2021-04-04 22:49:51 +02:00
7a61f7e2da Merge branch 'dev' into add-turn-server 2021-04-04 22:49:36 +02:00
83570a15ca iceservers join with iceserver. 2021-04-04 22:48:54 +02:00
f85d4d312f go fmt whole project. 2021-04-04 22:37:33 +02:00
c7a178e5a4 object instead of string 2021-04-04 20:08:28 +00:00
29b4881c08 allow to add password protected turn server 2021-04-04 17:37:07 +00:00
90bc4b3280 downgrade emojilib and recreate emojis, fixes #41. 2021-04-04 17:17:47 +02:00
abd1a76965 shake keyboard icon. 2021-04-04 16:57:23 +02:00
a7ab37a4f5 dense chat messages. 2021-04-04 16:10:56 +02:00
450cfe134d fix emojis #41. 2021-04-04 15:34:01 +02:00
cf06641f19 fix lock i18n swap bug. 2021-04-04 15:06:22 +02:00
e27b98d509 lint fix. 2021-04-04 00:16:36 +02:00
cf401b4187 Rename sv_se.ts to sv-se.ts 2021-04-03 22:56:18 +02:00
73ea442d18 Update index.ts 2021-04-03 22:56:03 +02:00
7a1716ebd0 Swedish translation
I made a swedish translation for neko. I am not a professional translator but fluent in swedish and I hope I got the most correct.
2021-04-03 22:53:43 +02:00
8e18465409 add cast query param. 2021-04-03 22:25:11 +02:00
9d514206ca auto user login. 2021-04-03 22:00:32 +02:00
391417d1ea fix vlc fullscreen mode. 2021-04-03 19:19:25 +02:00
94f1cd5c38 add tor-browser. 2021-04-03 17:55:27 +02:00
0076637353 upgrade slovak translation. 2021-04-03 16:00:29 +02:00
8a0d0bac0c translation key 'you' should be optional. 2021-04-03 16:00:07 +02:00
6c4cc0cc89 allow dev server. 2021-04-03 15:59:34 +02:00
48a6cd6ad5 Merge branch 'dev' into sk_lang 2021-04-03 15:20:27 +02:00
3c92477a05 remove unused CodecName from pipelines. 2021-04-03 15:19:01 +02:00
524e70f6c4 dev: -i flag is deprecated. 2021-04-03 15:17:50 +02:00
ce52331d68 broadcast pipeline -> optional arguments. 2021-04-03 15:17:26 +02:00
5805cbdda5 Merge pull request #40 from mbattista/arm-dockerfiles
ARM container do need their own Dockerfile
2021-04-03 14:58:41 +02:00
657d0f55f4 arm chromium remove unused package. 2021-04-03 14:20:43 +02:00
c501b108de fix chromium path. 2021-04-03 14:18:38 +02:00
d530be49f3 chromium on debian buster 2021-04-03 13:11:00 +01:00
5931123bb3 arm build with common dockerfile. 2021-04-03 13:56:41 +02:00
f9aaabd831 change ARM Dockerfile base & names. 2021-04-03 13:50:54 +02:00
208735a305 ARM container do need their own Dockerfile 2021-04-03 12:10:42 +01:00
56e153b277 Merge pull request #38 from whalehub/update
Update Ungoogled Chromium and extensions
2021-04-02 22:31:35 +02:00
6cd9446ec7 Update Ungoogled Chromium and extensions
Ungoogled Chromium updated to 89.0.4389.114
uBlock Origin updated to 1.34.0
NordVPN updated to 2.29.1

Signed-off-by: Aaron <admin@datahoarder.dev>
2021-04-02 22:26:20 +02:00
b264e397d8 add language picker. 2021-04-02 15:29:27 +00:00
fd4b4bbfa2 Merge branch 'pr-37' into dev 2021-04-02 15:10:21 +00:00
700d43710e lint fix. 2021-04-02 15:06:13 +00:00
3989635b90 Change the way we export (breaking change) 2021-03-29 11:12:07 +00:00
99d528f2ea translations 2021-03-29 11:11:37 +00:00
78d2d706af wrong condition 2021-03-29 11:11:26 +00:00
78a1744da4 Merge pull request #1 from m1k1o/dev
Update from remote dev
2021-03-29 13:04:05 +02:00
8efc5d7094 merge from remote 2021-03-29 11:03:25 +00:00
a1fcf87345 dev branch 2021-03-29 10:55:31 +00:00
bac0686f20 Merge branch 'master' of https://github.com/nurdism/neko into sk_lang 2020-07-11 23:18:28 +02:00
c146534ae1 Merge branch 'master' of https://github.com/nurdism/neko into sk_lang 2020-06-09 14:55:21 +02:00
6a7327c238 SK translation fixed 2020-04-20 21:11:58 +02:00
1ce276f313 Merge branch 'master' of github.com:m1k1o/neko into sk_lang 2020-04-20 21:05:55 +02:00
ee2f27f80a initial SK support 2020-04-06 12:44:47 +02:00
104 changed files with 6958 additions and 2415 deletions

76
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,76 @@
name: "CI for builds"
on:
push:
branches: [ dev ]
#
# Run this action periodically to keep browsers up-to-date
# even if there is no activity in this repo.
#
schedule:
- cron: "43 2 * * 1"
env:
DOCKER_IMAGE: m1k1o/neko
jobs:
build-base:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
run: |
docker login --username "${DOCKER_USERNAME}" --password-stdin "${DOCKER_REGISTRY}" <<< "${DOCKER_TOKEN}"
env:
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
- name: Build base
run: |
BUILD_IMAGE=${DOCKER_IMAGE} .m1k1o/build ${DOCKER_TAG}
docker push ${DOCKER_IMAGE}:${DOCKER_TAG}
env:
DOCKER_TAG: base
build:
runs-on: ubuntu-latest
#
# do not run on forks
#
if: github.repository_owner == 'm1k1o'
needs: [ build-base ]
strategy:
matrix:
tags: [ firefox, chromium, google-chrome, ungoogled-chromium, brave, tor-browser, vncviewer, vlc, xfce ]
env:
DOCKER_TAG: ${{ matrix.tags }}
steps:
- name: Check Out Repo
uses: actions/checkout@v2
- name: Login to Docker Hub
run: |
docker login --username "${DOCKER_USERNAME}" --password-stdin "${DOCKER_REGISTRY}" <<< "${DOCKER_TOKEN}"
env:
DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
- name: Build container
run: |
BUILD_IMAGE=${DOCKER_IMAGE} .m1k1o/build ${DOCKER_TAG}
docker push ${DOCKER_IMAGE}:${DOCKER_TAG}
- name: Push latest tag
if: ${{ matrix.tags == 'firefox' }}
run: |
docker pull ${DOCKER_IMAGE}:${DOCKER_TAG}
docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest
docker push ${DOCKER_IMAGE}:latest

View File

@ -13,6 +13,7 @@ If you want, you can build other tags. `base` tag needs to be build first:
- `./build base`
- `./build firefox`
- `./build chromium`
- `./build google-chrome`
- etc...
## Step 2: Starting server

View File

@ -1,7 +1,7 @@
#
# STAGE 1: SERVER
#
FROM golang:1.16-buster as server
FROM golang:1.17-bullseye as server
WORKDIR /src
#
@ -32,7 +32,7 @@ RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
#
# STAGE 2: CLIENT
#
FROM node:14-buster-slim as client
FROM node:14-bullseye-slim as client
WORKDIR /src
#
@ -48,7 +48,7 @@ RUN npm run build
#
# STAGE 3: RUNTIME
#
FROM debian:buster-slim
FROM debian:bullseye-slim
#
# avoid warnings by switching to noninteractive
@ -65,7 +65,7 @@ ARG USER_GID=$USER_UID
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx5; \
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6; \
#
# gst
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \

View File

@ -1,7 +1,7 @@
#
# STAGE 1: SERVER
#
FROM arm32v7/golang:1.16-buster as server
FROM arm32v7/golang:1.17-buster as server
WORKDIR /src
#

View File

@ -1,5 +1,6 @@
[supervisord]
nodaemon=true
user=root
pidfile=/var/run/supervisord.pid
logfile=/dev/null
logfile_maxbytes=0
@ -44,6 +45,8 @@ redirect_stderr=true
[program:neko]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/neko serve --static "/var/www"
stopsignal=INT
stopwaitsecs=5
autorestart=true
priority=800
user=%(ENV_USER)s

23
.m1k1o/brave/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends apt-transport-https curl openbox; \
#
# install brave browser
curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg; \
echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg arch=amd64] https://brave-browser-apt-release.s3.brave.com/ stable main" \
| tee /etc/apt/sources.list.d/brave-browser-release.list; \
apt-get update; \
apt-get install -y --no-install-recommends brave-browser; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/brave.conf
COPY --chown=neko preferences.json /home/neko/.config/brave/Default/Preferences
COPY policies.json /etc/brave/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

763
.m1k1o/brave/openbox.xml Normal file
View File

@ -0,0 +1,763 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Default openbox config but all window decorations are moved
thereby making it harder to accidentally close the virtual browser -->
<openbox_config xmlns="http://openbox.org/3.4/rc"
xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance>
<strength>10</strength>
<screen_edge_strength>20</screen_edge_strength>
</resistance>
<applications>
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
<application class="Brave-browser*" name="brave-browser*">
<decor>no</decor>
<maximized>true</maximized>
<focus>yes</focus>
<layer>normal</layer>
</application>
</applications>
<focus>
<focusNew>yes</focusNew>
<!-- always try to focus new windows when they appear. other rules do
apply -->
<followMouse>no</followMouse>
<!-- move focus to a window when you move the mouse into it -->
<focusLast>yes</focusLast>
<!-- focus the last used window when changing desktops, instead of the one
under the mouse pointer. when followMouse is enabled -->
<underMouse>no</underMouse>
<!-- move focus under the mouse, even when the mouse is not moving -->
<focusDelay>200</focusDelay>
<!-- when followMouse is enabled, the mouse must be inside the window for
this many milliseconds (1000 = 1 sec) before moving focus to it -->
<raiseOnFocus>no</raiseOnFocus>
<!-- when followMouse is enabled, and a window is given focus by moving the
mouse into it, also raise the window -->
</focus>
<placement>
<policy>Smart</policy>
<!-- 'Smart' or 'UnderMouse' -->
<center>yes</center>
<!-- whether to place windows in the center of the free area found or
the top left corner -->
<monitor>Primary</monitor>
<!-- with Smart placement on a multi-monitor system, try to place new windows
on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
the active window is, 'Primary' - only on the primary monitor -->
<primaryMonitor>1</primaryMonitor>
<!-- The monitor where Openbox should place popup dialogs such as the
focus cycling popup, or the desktop switch popup. It can be an index
from 1, specifying a particular monitor. Or it can be one of the
following: 'Mouse' - where the mouse is, or
'Active' - where the active window is -->
</placement>
<theme>
<name>Clearlooks</name>
<titleLayout>NLIMC</titleLayout>
<!--
available characters are NDSLIMC, each can occur at most once.
N: window icon
L: window label (AKA title).
I: iconify
M: maximize
C: close
S: shade (roll up/down)
D: omnipresent (on all desktops).
-->
<keepBorder>yes</keepBorder>
<animateIconify>yes</animateIconify>
<font place="ActiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuHeader">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuItem">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="ActiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
</theme>
<desktops>
<!-- this stuff is only used at startup, pagers allow you to change them
during a session
these are default values to use when other ones are not already set
by other applications, or saved in your session
use obconf if you want to change these without having to log out
and back in -->
<number>1</number>
<firstdesk>1</firstdesk>
<names>
<!-- set names up here if you want to, like this:
<name>desktop 1</name>
<name>desktop 2</name>
-->
</names>
<popupTime>875</popupTime>
<!-- The number of milliseconds to show the popup for when switching
desktops. Set this to 0 to disable the popup. -->
</desktops>
<resize>
<drawContents>yes</drawContents>
<popupShow>Nonpixel</popupShow>
<!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
<popupPosition>Center</popupPosition>
<!-- 'Center', 'Top', or 'Fixed' -->
<popupFixedPosition>
<!-- these are used if popupPosition is set to 'Fixed' -->
<x>10</x>
<!-- positive number for distance from left edge, negative number for
distance from right edge, or 'Center' -->
<y>10</y>
<!-- positive number for distance from top edge, negative number for
distance from bottom edge, or 'Center' -->
</popupFixedPosition>
</resize>
<!-- You can reserve a portion of your screen where windows will not cover when
they are maximized, or when they are initially placed.
Many programs reserve space automatically, but you can use this in other
cases. -->
<margins>
<top>0</top>
<bottom>0</bottom>
<left>0</left>
<right>0</right>
</margins>
<dock>
<position>TopLeft</position>
<!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
<floatingX>0</floatingX>
<floatingY>0</floatingY>
<noStrut>no</noStrut>
<stacking>Above</stacking>
<!-- 'Above', 'Normal', or 'Below' -->
<direction>Vertical</direction>
<!-- 'Vertical' or 'Horizontal' -->
<autoHide>no</autoHide>
<hideDelay>300</hideDelay>
<!-- in milliseconds (1000 = 1 second) -->
<showDelay>300</showDelay>
<!-- in milliseconds (1000 = 1 second) -->
<moveButton>Middle</moveButton>
<!-- 'Left', 'Middle', 'Right' -->
</dock>
<keyboard>
<chainQuitKey>C-g</chainQuitKey>
<!-- Keybindings for desktop switching -->
<keybind key="C-A-Left">
<action name="GoToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Right">
<action name="GoToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Up">
<action name="GoToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Down">
<action name="GoToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Left">
<action name="SendToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Right">
<action name="SendToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Up">
<action name="SendToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Down">
<action name="SendToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="W-F1">
<action name="GoToDesktop"><to>1</to></action>
</keybind>
<keybind key="W-F2">
<action name="GoToDesktop"><to>2</to></action>
</keybind>
<keybind key="W-F3">
<action name="GoToDesktop"><to>3</to></action>
</keybind>
<keybind key="W-F4">
<action name="GoToDesktop"><to>4</to></action>
</keybind>
<keybind key="W-d">
<action name="ToggleShowDesktop"/>
</keybind>
<!-- Keybindings for windows -->
<keybind key="A-F4">
<action name="Close"/>
</keybind>
<keybind key="A-Escape">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</keybind>
<keybind key="A-space">
<!--action name="ShowMenu"><menu>client-menu</menu></action-->
</keybind>
<!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
<keybind key="A-Print">
<action name="Execute"><command>scrot -s</command></action>
</keybind>
<!-- Keybindings for window switching -->
<keybind key="A-Tab">
<action name="NextWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="A-S-Tab">
<action name="PreviousWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="C-A-Tab">
<action name="NextWindow">
<panels>yes</panels><desktop>yes</desktop>
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<!-- Keybindings for window switching with the arrow keys -->
<keybind key="W-S-Right">
<action name="DirectionalCycleWindows">
<direction>right</direction>
</action>
</keybind>
<keybind key="W-S-Left">
<action name="DirectionalCycleWindows">
<direction>left</direction>
</action>
</keybind>
<keybind key="W-S-Up">
<action name="DirectionalCycleWindows">
<direction>up</direction>
</action>
</keybind>
<keybind key="W-S-Down">
<action name="DirectionalCycleWindows">
<direction>down</direction>
</action>
</keybind>
<!-- Keybindings for running applications -->
<keybind key="W-e">
<action name="Execute">
<startupnotify>
<enabled>true</enabled>
<name>Konqueror</name>
</startupnotify>
<command>kfmclient openProfile filemanagement</command>
</action>
</keybind>
<!-- Launch scrot when Print is pressed -->
<keybind key="Print">
<action name="Execute"><command>scrot</command></action>
</keybind>
</keyboard>
<mouse>
<dragThreshold>1</dragThreshold>
<!-- number of pixels the mouse must move before a drag begins -->
<doubleClickTime>500</doubleClickTime>
<!-- in milliseconds (1000 = 1 second) -->
<screenEdgeWarpTime>400</screenEdgeWarpTime>
<!-- Time before changing desktops when the pointer touches the edge of the
screen while moving a window, in milliseconds (1000 = 1 second).
Set this to 0 to disable warping -->
<screenEdgeWarpMouse>false</screenEdgeWarpMouse>
<!-- Set this to TRUE to move the mouse pointer across the desktop when
switching due to hitting the edge of the screen -->
<context name="Frame">
<mousebind button="A-Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="A-Left" action="Click">
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="A-Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Right" action="Drag">
<action name="Resize"/>
</mousebind>
<mousebind button="A-Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-S-Up" action="Click">
<action name="SendToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-S-Down" action="Click">
<action name="SendToDesktop"><to>next</to></action>
</mousebind>
</context>
<context name="Titlebar">
<mousebind button="Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="Left" action="DoubleClick">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Up" action="Click">
<action name="if">
<shaded>no</shaded>
<then>
<action name="Shade"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
<action name="Lower"/>
</then>
</action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="if">
<shaded>yes</shaded>
<then>
<action name="Unshade"/>
<action name="Raise"/>
</then>
</action>
</mousebind>
</context>
<context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="Top">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>top</edge></action>
</mousebind>
</context>
<context name="Left">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>left</edge></action>
</mousebind>
</context>
<context name="Right">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>right</edge></action>
</mousebind>
</context>
<context name="Bottom">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>bottom</edge></action>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="TRCorner BRCorner TLCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Drag">
<action name="Resize"/>
</mousebind>
</context>
<context name="Client">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Icon">
<!--mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="AllDesktops">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleOmnipresent"/>
</mousebind>
</context>
<context name="Shade">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleShade"/>
</mousebind>
</context>
<context name="Iconify">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Iconify"/>
</mousebind>
</context>
<context name="Maximize">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Middle" action="Click">
<action name="ToggleMaximize"><direction>vertical</direction></action>
</mousebind>
<mousebind button="Right" action="Click">
<action name="ToggleMaximize"><direction>horizontal</direction></action>
</mousebind>
</context>
<context name="Close">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Close"/>
</mousebind>
</context>
<context name="Desktop">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Root">
<!-- Menus -->
<!--mousebind button="Middle" action="Press">
<action name="ShowMenu"><menu>client-list-combined-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="ShowMenu"><menu>root-menu</menu></action>
</mousebind-->
</context>
<context name="MoveResize">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
</context>
</mouse>
<menu>
<!-- You can specify more than one menu file in here and they are all loaded,
just don't make menu ids clash or, well, it'll be kind of pointless -->
<!-- default menu file (or custom one in $HOME/.config/openbox/) -->
<!-- system menu files on Debian systems -->
<!--file>/var/lib/openbox/debian-menu.xml</file-->
<file>menu.xml</file>
<hideDelay>200</hideDelay>
<!-- if a press-release lasts longer than this setting (in milliseconds), the
menu is hidden again -->
<middle>no</middle>
<!-- center submenus vertically about the parent entry -->
<submenuShowDelay>100</submenuShowDelay>
<!-- time to delay before showing a submenu after hovering over the parent
entry.
if this is a negative value, then the delay is infinite and the
submenu will not be shown until it is clicked on -->
<submenuHideDelay>400</submenuHideDelay>
<!-- time to delay before hiding a submenu when selecting another
entry in parent menu
if this is a negative value, then the delay is infinite and the
submenu will not be hidden until a different submenu is opened -->
<showIcons>yes</showIcons>
<!-- controls if icons appear in the client-list-(combined-)menu -->
<manageDesktops>yes</manageDesktops>
<!-- show the manage desktops section in the client-list-(combined-)menu -->
</menu>
<applications>
<!--
# this is an example with comments through out. use these to make your
# own rules, but without the comments of course.
# you may use one or more of the name/class/role/title/type rules to specify
# windows to match
<application name="the window's _OB_APP_NAME property (see obxprop)"
class="the window's _OB_APP_CLASS property (see obxprop)"
groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
role="the window's _OB_APP_ROLE property (see obxprop)"
title="the window's _OB_APP_TITLE property (see obxprop)"
type="the window's _OB_APP_TYPE property (see obxprob)..
(if unspecified, then it is 'dialog' for child windows)">
# you may set only one of name/class/role/title/type, or you may use more
# than one together to restrict your matches.
# the name, class, role, and title use simple wildcard matching such as those
# used by a shell. you can use * to match any characters and ? to match
# any single character.
# the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
# or desktop
# when multiple rules match a window, they will all be applied, in the
# order that they appear in this list
# each rule element can be left out or set to 'default' to specify to not
# change that attribute of the window
<decor>yes</decor>
# enable or disable window decorations
<shade>no</shade>
# make the window shaded when it appears, or not
<position force="no">
# the position is only used if both an x and y coordinate are provided
# (and not set to 'default')
# when force is "yes", then the window will be placed here even if it
# says you want it placed elsewhere. this is to override buggy
# applications who refuse to behave
<x>center</x>
# a number like 50, or 'center' to center on screen. use a negative number
# to start from the right (or bottom for <y>), ie -50 is 50 pixels from
# the right edge (or bottom). use 'default' to specify using value
# provided by the application, or chosen by openbox, instead.
<y>200</y>
<monitor>1</monitor>
# specifies the monitor in a xinerama setup.
# 1 is the first head, or 'mouse' for wherever the mouse is
</position>
<size>
# the size to make the window.
<width>20</width>
# a number like 20, or 'default' to use the size given by the application.
# you can use fractions such as 1/2 or percentages such as 75% in which
# case the value is relative to the size of the monitor that the window
# appears on.
<height>30%</height>
</size>
<focus>yes</focus>
# if the window should try be given focus when it appears. if this is set
# to yes it doesn't guarantee the window will be given focus. some
# restrictions may apply, but Openbox will try to
<desktop>1</desktop>
# 1 is the first desktop, 'all' for all desktops
<layer>normal</layer>
# 'above', 'normal', or 'below'
<iconic>no</iconic>
# make the window iconified when it appears, or not
<skip_pager>no</skip_pager>
# asks to not be shown in pagers
<skip_taskbar>no</skip_taskbar>
# asks to not be shown in taskbars. window cycling actions will also
# skip past such windows
<fullscreen>yes</fullscreen>
# make the window in fullscreen mode when it appears
<maximized>true</maximized>
# 'Horizontal', 'Vertical' or boolean (yes/no)
</application>
# end of the example
-->
</applications>
</openbox_config>

View File

@ -0,0 +1,38 @@
{
"HomepageLocation": "",
"AutoFillEnabled": false,
"AutofillAddressEnabled": false,
"AutofillCreditCardEnabled": false,
"BrowserSignin": 0,
"DefaultNotificationsSetting": 2,
"DeveloperToolsAvailability": 2,
"EditBookmarksEnabled": false,
"FullscreenAllowed": true,
"IncognitoModeAvailability": 1,
"SyncDisabled": true,
"AutoplayAllowed": true,
"BrowserAddPersonEnabled": false,
"BrowserGuestModeEnabled": false,
"DefaultPopupsSetting": 2,
"DownloadRestrictions": 3,
"VideoCaptureAllowed": true,
"AllowFileSelectionDialogs": false,
"PromptForDownloadLocation": false,
"BookmarkBarEnabled": false,
"PasswordManagerEnabled": false,
"URLBlacklist": [
"file://*",
"chrome://policy"
],
"ExtensionInstallForcelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
"fjoaledfpmneenckfbpdfhkmimnjocfa;https://clients2.google.com/service/update2/crx"
],
"ExtensionInstallWhitelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
"fjoaledfpmneenckfbpdfhkmimnjocfa"
],
"ExtensionInstallBlacklist": [
"*"
]
}

View File

@ -0,0 +1,110 @@
{
"homepage": "http://www.google.com",
"homepage_is_newtabpage": false,
"first_run_tabs": [
"https://www.google.com/_/chrome/newtab?ie=UTF-8"
],
"custom_links": {
"initialized": true,
"list": [
{
"title": "YouTube",
"url": "https://www.youtube.com/"
},
{
"title": "Netflix",
"url": "https://netflix.com"
},
{
"title": "Hulu",
"url": "https://www.hulu.com/"
},
{
"title": "9Anime",
"url": "https://9anime.to/"
},
{
"title": "Crunchy Roll",
"url": "https://www.crunchyroll.com/"
},
{
"title": "Funimation",
"url": "https://www.funimation.com/"
},
{
"title": "Disney+",
"url": "https://www.disneyplus.com/"
},
{
"title": "HBO Now",
"url": "https://play.hbonow.com/"
},
{
"title": "Amazon Video",
"url": "https://www.amazon.com/Amazon-Video/b?node=2858778011"
},
{
"title": "VRV",
"url": "https://vrv.co/"
},
{
"title": "Twitch",
"url": "https://www.twitch.tv/"
},
{
"title": "Mixer",
"url": "https://mixer.com/"
}
]
},
"browser": {
"custom_chrome_frame": false,
"show_home_button": true,
"window_placement": {
"maximized": true
}
},
"bookmark_bar": {
"show_on_all_tabs": false
},
"sync_promo": {
"show_on_first_run_allowed": false
},
"distribution": {
"import_bookmarks_from_file": "bookmarks.html",
"import_bookmarks": true,
"import_history": true,
"import_home_page": true,
"import_search_engine": true,
"ping_delay": 60,
"do_not_create_desktop_shortcut": true,
"do_not_create_quick_launch_shortcut": true,
"do_not_create_taskbar_shortcut": true,
"do_not_launch_chrome": true,
"do_not_register_for_update_launch": true,
"make_chrome_default": true,
"make_chrome_default_for_user": true,
"system_level": false,
"verbose_logging": false
},
"profile": {
"avatar_index": 19,
"default_content_setting_values": {
"clipboard": 2,
"cookies": 4,
"geolocation": 2,
"media_stream_camera": 2,
"media_stream_mic": 2,
"midi_sysex": 2,
"payment_handler": 2,
"usb_guard": 2
},
"name": "neko",
"using_default_avatar": false,
"using_default_name": false,
"using_gaia_avatar": false
},
"signin": {
"allowed": false
}
}

View File

@ -0,0 +1,21 @@
[program:brave]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/brave-browser --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/brave --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/brave.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -3,6 +3,12 @@ cd "$(dirname "$0")"
BASE="${PWD}/../"
# BUILD_IMAGE from environment vairables has precedence
if [ ! -z "${BUILD_IMAGE}" ]
then
ENV_BUILD_IMAGE="${BUILD_IMAGE}"
fi
if [ -f ".env.default" ]
then
export $(cat .env.default | sed 's/#.*//g' | xargs)
@ -13,6 +19,13 @@ then
export $(cat .env | sed 's/#.*//g' | xargs)
fi
# BUILD_IMAGE from environment vairables has precedence
if [ ! -z "${ENV_BUILD_IMAGE}" ]
then
BUILD_IMAGE="${ENV_BUILD_IMAGE}"
unset ENV_BUILD_IMAGE
fi
if [ -z "${1}" ] && [ ! -z "${SERVER_TAG}" ]
then
./build base
@ -43,7 +56,7 @@ build() {
# build base
docker build -t "${BUILD_IMAGE}:base" -f base/Dockerfile "${BASE}"
else
# buld image
# build image
docker build -t "${BUILD_IMAGE}:$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:base" -f "$1/Dockerfile" "$1/"
fi
}
@ -52,9 +65,13 @@ build_arm() {
if [ "$1" = "base" ]
then
# build ARM base
docker build -t "${BUILD_IMAGE}:arm-base" -f arm-base/Dockerfile "${BASE}"
docker build -t "${BUILD_IMAGE}:arm-base" -f base/Dockerfile.arm "${BASE}"
elif [ -f "$1/Dockerfile.arm" ]
then
# build dedicated ARM image
docker build -t "${BUILD_IMAGE}:arm-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:arm-base" -f "$1/Dockerfile.arm" "$1/"
else
# buld ARM image
# try to build ARM image with common Dockerfile
docker build -t "${BUILD_IMAGE}:arm-$1" --build-arg="BASE_IMAGE=${BUILD_IMAGE}:arm-base" -f "$1/Dockerfile" "$1/"
fi
}

View File

@ -4,7 +4,7 @@ FROM $BASE_IMAGE
#
# install neko chromium
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends unzip chromium openbox; \
apt-get install -y --no-install-recommends unzip chromium chromium-sandbox openbox; \
#
# install widevine module
WIDEVINE_VERSION=$(wget --quiet -O - https://dl.google.com/widevine-cdm/versions.txt | tail -n 1); \

View File

@ -0,0 +1,19 @@
ARG BASE_IMAGE=m1k1o/neko:arm-base
FROM $BASE_IMAGE
#
# install neko chromium
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends chromium-browser openbox libwidevinecdm0; \
ln -s /usr/bin/chromium-browser /usr/bin/chromium; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/chromium.conf
COPY --chown=neko preferences.json /home/neko/.config/chromium/Default/Preferences
COPY policies.json /etc/chromium/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -1,24 +1,35 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG SRC_URL="https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US"
#
# install firefox-esr
# install firefox
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox firefox-esr; \
apt-get install -y --no-install-recommends openbox \
xz-utils bzip2 libgtk-3-0 libdbus-glib-1-2; \
#
# install extensions
mkdir -p /usr/lib/firefox-esr/distribution/extensions; \
wget -O '/usr/lib/firefox-esr/distribution/extensions/uBlock0@raymondhill.net.xpi' https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi; \
wget -O /usr/lib/firefox-esr/distribution/extensions/nordvpnproxy@nordvpn.com.xpi https://addons.mozilla.org/firefox/downloads/latest/nordvpn-proxy-extension/latest.xpi; \
# fetch latest release
wget -O /tmp/firefox-setup.tar.bz2 "${SRC_URL}"; \
mkdir /usr/lib/firefox; \
tar -xjf /tmp/firefox-setup.tar.bz2 -C /usr/lib; \
rm -f /tmp/firefox-setup.tar.bz2; \
ln -s /usr/lib/firefox/firefox /usr/bin/firefox; \
#
# create a profile directory
mkdir -p /home/neko/.mozilla/firefox/profile.default/extensions; \
chown -R neko:neko /home/neko/.mozilla/firefox/profile.default; \
#
# clean up
apt-get --purge autoremove -y xz-utils bzip2; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/firefox.conf
COPY neko.js /usr/lib/firefox-esr/mozilla.cfg
COPY autoconfig.js /usr/lib/firefox-esr/defaults/pref/autoconfig.js
COPY policies.json /usr/lib/firefox-esr/distribution/policies.json
COPY neko.js /usr/lib/firefox/mozilla.cfg
COPY autoconfig.js /usr/lib/firefox/defaults/pref/autoconfig.js
COPY policies.json /usr/lib/firefox/distribution/policies.json
COPY --chown=neko profiles.ini /home/neko/.mozilla/firefox/profiles.ini
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -0,0 +1,30 @@
ARG BASE_IMAGE=m1k1o/neko:arm-base
FROM $BASE_IMAGE
#
# install firefox-esr
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox firefox-esr libwidevinecdm0; \
ln -s /usr/lib/firefox-esr/firefox-esr /usr/bin/firefox; \
#
# install extensions
mkdir -p /usr/lib/firefox-esr/distribution/extensions; \
wget -O '/usr/lib/firefox-esr/distribution/extensions/uBlock0@raymondhill.net.xpi' https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi; \
wget -O /usr/lib/firefox-esr/distribution/extensions/nordvpnproxy@nordvpn.com.xpi https://addons.mozilla.org/firefox/downloads/latest/nordvpn-proxy-extension/latest.xpi; \
#
# create a profile directory
mkdir -p /home/neko/.mozilla/firefox/profile.default/extensions; \
chown -R neko:neko /home/neko/.mozilla/firefox/profile.default; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/firefox.conf
COPY neko.js /usr/lib/firefox-esr/mozilla.cfg
COPY autoconfig.js /usr/lib/firefox-esr/defaults/pref/autoconfig.js
COPY policies.json /usr/lib/firefox-esr/distribution/policies.json
COPY --chown=neko profiles.ini /home/neko/.mozilla/firefox/profiles.ini
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -32,4 +32,6 @@ lockPref("devtools.theme", "dark");
lockPref("ui.systemUsesDarkTheme", 1);
lockPref("lightweightThemes.usedThemes","[]");
lockPref("lightweightThemes.selectedThemeID", "firefox-compact-dark@mozilla.org");
lockPref("extensions.activeThemeID", "firefox-compact-dark@mozilla.org");
lockPref("browser.theme.toolbar-theme", 0);
lockPref("browser.in-content.dark-mode", true);

View File

@ -77,7 +77,7 @@
"HardwareAcceleration": false,
"Homepage": {
"Additional": [],
"StartPage": "none"
"StartPage": "home"
},
"NewTabPage": true,
"NoDefaultBookmarks": true,

View File

@ -0,0 +1,8 @@
[General]
StartWithLastProfile=1
[Profile0]
Name=default
IsRelative=1
Path=profile.default
Default=1

View File

@ -1,10 +1,10 @@
[program:firefox-esr]
[program:firefox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/lib/firefox-esr/firefox-esr --display=%(ENV_DISPLAY)s -setDefaultBrowser -width 1280 -height 720
command=/usr/bin/firefox --no-remote -P default --display=%(ENV_DISPLAY)s -setDefaultBrowser -width 1280 -height 720
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/firefox-esr.log
stdout_logfile=/var/log/neko/firefox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -0,0 +1,21 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG SRC_URL="https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
#
# install google chrome
RUN set -eux; apt-get update; \
wget -O /tmp/google-chrome.deb "${SRC_URL}"; \
apt-get install -y --no-install-recommends openbox /tmp/google-chrome.deb; \
#
# clean up
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
#
# copy configuation files
COPY supervisord.conf /etc/neko/supervisord/google-chrome.conf
COPY --chown=neko preferences.json /home/neko/.config/google-chrome/Default/Preferences
COPY policies.json /etc/opt/chrome/policies/managed/policies.json
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -0,0 +1,763 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Default openbox config but all window decorations are moved
thereby making it harder to accidentally close the virtual browser -->
<openbox_config xmlns="http://openbox.org/3.4/rc"
xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance>
<strength>10</strength>
<screen_edge_strength>20</screen_edge_strength>
</resistance>
<applications>
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
<application class="Google-chrome*" name="google-chrome*">
<decor>no</decor>
<maximized>true</maximized>
<focus>yes</focus>
<layer>normal</layer>
</application>
</applications>
<focus>
<focusNew>yes</focusNew>
<!-- always try to focus new windows when they appear. other rules do
apply -->
<followMouse>no</followMouse>
<!-- move focus to a window when you move the mouse into it -->
<focusLast>yes</focusLast>
<!-- focus the last used window when changing desktops, instead of the one
under the mouse pointer. when followMouse is enabled -->
<underMouse>no</underMouse>
<!-- move focus under the mouse, even when the mouse is not moving -->
<focusDelay>200</focusDelay>
<!-- when followMouse is enabled, the mouse must be inside the window for
this many milliseconds (1000 = 1 sec) before moving focus to it -->
<raiseOnFocus>no</raiseOnFocus>
<!-- when followMouse is enabled, and a window is given focus by moving the
mouse into it, also raise the window -->
</focus>
<placement>
<policy>Smart</policy>
<!-- 'Smart' or 'UnderMouse' -->
<center>yes</center>
<!-- whether to place windows in the center of the free area found or
the top left corner -->
<monitor>Primary</monitor>
<!-- with Smart placement on a multi-monitor system, try to place new windows
on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
the active window is, 'Primary' - only on the primary monitor -->
<primaryMonitor>1</primaryMonitor>
<!-- The monitor where Openbox should place popup dialogs such as the
focus cycling popup, or the desktop switch popup. It can be an index
from 1, specifying a particular monitor. Or it can be one of the
following: 'Mouse' - where the mouse is, or
'Active' - where the active window is -->
</placement>
<theme>
<name>Clearlooks</name>
<titleLayout>NLIMC</titleLayout>
<!--
available characters are NDSLIMC, each can occur at most once.
N: window icon
L: window label (AKA title).
I: iconify
M: maximize
C: close
S: shade (roll up/down)
D: omnipresent (on all desktops).
-->
<keepBorder>yes</keepBorder>
<animateIconify>yes</animateIconify>
<font place="ActiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuHeader">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuItem">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="ActiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
</theme>
<desktops>
<!-- this stuff is only used at startup, pagers allow you to change them
during a session
these are default values to use when other ones are not already set
by other applications, or saved in your session
use obconf if you want to change these without having to log out
and back in -->
<number>1</number>
<firstdesk>1</firstdesk>
<names>
<!-- set names up here if you want to, like this:
<name>desktop 1</name>
<name>desktop 2</name>
-->
</names>
<popupTime>875</popupTime>
<!-- The number of milliseconds to show the popup for when switching
desktops. Set this to 0 to disable the popup. -->
</desktops>
<resize>
<drawContents>yes</drawContents>
<popupShow>Nonpixel</popupShow>
<!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
<popupPosition>Center</popupPosition>
<!-- 'Center', 'Top', or 'Fixed' -->
<popupFixedPosition>
<!-- these are used if popupPosition is set to 'Fixed' -->
<x>10</x>
<!-- positive number for distance from left edge, negative number for
distance from right edge, or 'Center' -->
<y>10</y>
<!-- positive number for distance from top edge, negative number for
distance from bottom edge, or 'Center' -->
</popupFixedPosition>
</resize>
<!-- You can reserve a portion of your screen where windows will not cover when
they are maximized, or when they are initially placed.
Many programs reserve space automatically, but you can use this in other
cases. -->
<margins>
<top>0</top>
<bottom>0</bottom>
<left>0</left>
<right>0</right>
</margins>
<dock>
<position>TopLeft</position>
<!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
<floatingX>0</floatingX>
<floatingY>0</floatingY>
<noStrut>no</noStrut>
<stacking>Above</stacking>
<!-- 'Above', 'Normal', or 'Below' -->
<direction>Vertical</direction>
<!-- 'Vertical' or 'Horizontal' -->
<autoHide>no</autoHide>
<hideDelay>300</hideDelay>
<!-- in milliseconds (1000 = 1 second) -->
<showDelay>300</showDelay>
<!-- in milliseconds (1000 = 1 second) -->
<moveButton>Middle</moveButton>
<!-- 'Left', 'Middle', 'Right' -->
</dock>
<keyboard>
<chainQuitKey>C-g</chainQuitKey>
<!-- Keybindings for desktop switching -->
<keybind key="C-A-Left">
<action name="GoToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Right">
<action name="GoToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Up">
<action name="GoToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Down">
<action name="GoToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Left">
<action name="SendToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Right">
<action name="SendToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Up">
<action name="SendToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Down">
<action name="SendToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="W-F1">
<action name="GoToDesktop"><to>1</to></action>
</keybind>
<keybind key="W-F2">
<action name="GoToDesktop"><to>2</to></action>
</keybind>
<keybind key="W-F3">
<action name="GoToDesktop"><to>3</to></action>
</keybind>
<keybind key="W-F4">
<action name="GoToDesktop"><to>4</to></action>
</keybind>
<keybind key="W-d">
<action name="ToggleShowDesktop"/>
</keybind>
<!-- Keybindings for windows -->
<keybind key="A-F4">
<action name="Close"/>
</keybind>
<keybind key="A-Escape">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</keybind>
<keybind key="A-space">
<!--action name="ShowMenu"><menu>client-menu</menu></action-->
</keybind>
<!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
<keybind key="A-Print">
<action name="Execute"><command>scrot -s</command></action>
</keybind>
<!-- Keybindings for window switching -->
<keybind key="A-Tab">
<action name="NextWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="A-S-Tab">
<action name="PreviousWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="C-A-Tab">
<action name="NextWindow">
<panels>yes</panels><desktop>yes</desktop>
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<!-- Keybindings for window switching with the arrow keys -->
<keybind key="W-S-Right">
<action name="DirectionalCycleWindows">
<direction>right</direction>
</action>
</keybind>
<keybind key="W-S-Left">
<action name="DirectionalCycleWindows">
<direction>left</direction>
</action>
</keybind>
<keybind key="W-S-Up">
<action name="DirectionalCycleWindows">
<direction>up</direction>
</action>
</keybind>
<keybind key="W-S-Down">
<action name="DirectionalCycleWindows">
<direction>down</direction>
</action>
</keybind>
<!-- Keybindings for running applications -->
<keybind key="W-e">
<action name="Execute">
<startupnotify>
<enabled>true</enabled>
<name>Konqueror</name>
</startupnotify>
<command>kfmclient openProfile filemanagement</command>
</action>
</keybind>
<!-- Launch scrot when Print is pressed -->
<keybind key="Print">
<action name="Execute"><command>scrot</command></action>
</keybind>
</keyboard>
<mouse>
<dragThreshold>1</dragThreshold>
<!-- number of pixels the mouse must move before a drag begins -->
<doubleClickTime>500</doubleClickTime>
<!-- in milliseconds (1000 = 1 second) -->
<screenEdgeWarpTime>400</screenEdgeWarpTime>
<!-- Time before changing desktops when the pointer touches the edge of the
screen while moving a window, in milliseconds (1000 = 1 second).
Set this to 0 to disable warping -->
<screenEdgeWarpMouse>false</screenEdgeWarpMouse>
<!-- Set this to TRUE to move the mouse pointer across the desktop when
switching due to hitting the edge of the screen -->
<context name="Frame">
<mousebind button="A-Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="A-Left" action="Click">
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="A-Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Right" action="Drag">
<action name="Resize"/>
</mousebind>
<mousebind button="A-Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-S-Up" action="Click">
<action name="SendToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-S-Down" action="Click">
<action name="SendToDesktop"><to>next</to></action>
</mousebind>
</context>
<context name="Titlebar">
<mousebind button="Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="Left" action="DoubleClick">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Up" action="Click">
<action name="if">
<shaded>no</shaded>
<then>
<action name="Shade"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
<action name="Lower"/>
</then>
</action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="if">
<shaded>yes</shaded>
<then>
<action name="Unshade"/>
<action name="Raise"/>
</then>
</action>
</mousebind>
</context>
<context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="Top">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>top</edge></action>
</mousebind>
</context>
<context name="Left">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>left</edge></action>
</mousebind>
</context>
<context name="Right">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>right</edge></action>
</mousebind>
</context>
<context name="Bottom">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>bottom</edge></action>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="TRCorner BRCorner TLCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Drag">
<action name="Resize"/>
</mousebind>
</context>
<context name="Client">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Icon">
<!--mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="AllDesktops">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleOmnipresent"/>
</mousebind>
</context>
<context name="Shade">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleShade"/>
</mousebind>
</context>
<context name="Iconify">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Iconify"/>
</mousebind>
</context>
<context name="Maximize">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Middle" action="Click">
<action name="ToggleMaximize"><direction>vertical</direction></action>
</mousebind>
<mousebind button="Right" action="Click">
<action name="ToggleMaximize"><direction>horizontal</direction></action>
</mousebind>
</context>
<context name="Close">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Close"/>
</mousebind>
</context>
<context name="Desktop">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Root">
<!-- Menus -->
<!--mousebind button="Middle" action="Press">
<action name="ShowMenu"><menu>client-list-combined-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="ShowMenu"><menu>root-menu</menu></action>
</mousebind-->
</context>
<context name="MoveResize">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
</context>
</mouse>
<menu>
<!-- You can specify more than one menu file in here and they are all loaded,
just don't make menu ids clash or, well, it'll be kind of pointless -->
<!-- default menu file (or custom one in $HOME/.config/openbox/) -->
<!-- system menu files on Debian systems -->
<!--file>/var/lib/openbox/debian-menu.xml</file-->
<file>menu.xml</file>
<hideDelay>200</hideDelay>
<!-- if a press-release lasts longer than this setting (in milliseconds), the
menu is hidden again -->
<middle>no</middle>
<!-- center submenus vertically about the parent entry -->
<submenuShowDelay>100</submenuShowDelay>
<!-- time to delay before showing a submenu after hovering over the parent
entry.
if this is a negative value, then the delay is infinite and the
submenu will not be shown until it is clicked on -->
<submenuHideDelay>400</submenuHideDelay>
<!-- time to delay before hiding a submenu when selecting another
entry in parent menu
if this is a negative value, then the delay is infinite and the
submenu will not be hidden until a different submenu is opened -->
<showIcons>yes</showIcons>
<!-- controls if icons appear in the client-list-(combined-)menu -->
<manageDesktops>yes</manageDesktops>
<!-- show the manage desktops section in the client-list-(combined-)menu -->
</menu>
<applications>
<!--
# this is an example with comments through out. use these to make your
# own rules, but without the comments of course.
# you may use one or more of the name/class/role/title/type rules to specify
# windows to match
<application name="the window's _OB_APP_NAME property (see obxprop)"
class="the window's _OB_APP_CLASS property (see obxprop)"
groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
role="the window's _OB_APP_ROLE property (see obxprop)"
title="the window's _OB_APP_TITLE property (see obxprop)"
type="the window's _OB_APP_TYPE property (see obxprob)..
(if unspecified, then it is 'dialog' for child windows)">
# you may set only one of name/class/role/title/type, or you may use more
# than one together to restrict your matches.
# the name, class, role, and title use simple wildcard matching such as those
# used by a shell. you can use * to match any characters and ? to match
# any single character.
# the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
# or desktop
# when multiple rules match a window, they will all be applied, in the
# order that they appear in this list
# each rule element can be left out or set to 'default' to specify to not
# change that attribute of the window
<decor>yes</decor>
# enable or disable window decorations
<shade>no</shade>
# make the window shaded when it appears, or not
<position force="no">
# the position is only used if both an x and y coordinate are provided
# (and not set to 'default')
# when force is "yes", then the window will be placed here even if it
# says you want it placed elsewhere. this is to override buggy
# applications who refuse to behave
<x>center</x>
# a number like 50, or 'center' to center on screen. use a negative number
# to start from the right (or bottom for <y>), ie -50 is 50 pixels from
# the right edge (or bottom). use 'default' to specify using value
# provided by the application, or chosen by openbox, instead.
<y>200</y>
<monitor>1</monitor>
# specifies the monitor in a xinerama setup.
# 1 is the first head, or 'mouse' for wherever the mouse is
</position>
<size>
# the size to make the window.
<width>20</width>
# a number like 20, or 'default' to use the size given by the application.
# you can use fractions such as 1/2 or percentages such as 75% in which
# case the value is relative to the size of the monitor that the window
# appears on.
<height>30%</height>
</size>
<focus>yes</focus>
# if the window should try be given focus when it appears. if this is set
# to yes it doesn't guarantee the window will be given focus. some
# restrictions may apply, but Openbox will try to
<desktop>1</desktop>
# 1 is the first desktop, 'all' for all desktops
<layer>normal</layer>
# 'above', 'normal', or 'below'
<iconic>no</iconic>
# make the window iconified when it appears, or not
<skip_pager>no</skip_pager>
# asks to not be shown in pagers
<skip_taskbar>no</skip_taskbar>
# asks to not be shown in taskbars. window cycling actions will also
# skip past such windows
<fullscreen>yes</fullscreen>
# make the window in fullscreen mode when it appears
<maximized>true</maximized>
# 'Horizontal', 'Vertical' or boolean (yes/no)
</application>
# end of the example
-->
</applications>
</openbox_config>

View File

@ -0,0 +1,38 @@
{
"HomepageLocation": "",
"AutoFillEnabled": false,
"AutofillAddressEnabled": false,
"AutofillCreditCardEnabled": false,
"BrowserSignin": 0,
"DefaultNotificationsSetting": 2,
"DeveloperToolsAvailability": 2,
"EditBookmarksEnabled": false,
"FullscreenAllowed": true,
"IncognitoModeAvailability": 1,
"SyncDisabled": true,
"AutoplayAllowed": true,
"BrowserAddPersonEnabled": false,
"BrowserGuestModeEnabled": false,
"DefaultPopupsSetting": 2,
"DownloadRestrictions": 3,
"VideoCaptureAllowed": true,
"AllowFileSelectionDialogs": false,
"PromptForDownloadLocation": false,
"BookmarkBarEnabled": false,
"PasswordManagerEnabled": false,
"URLBlacklist": [
"file://*",
"chrome://policy"
],
"ExtensionInstallForcelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx",
"fjoaledfpmneenckfbpdfhkmimnjocfa;https://clients2.google.com/service/update2/crx"
],
"ExtensionInstallWhitelist": [
"cjpalhdlnbpafiamejdnhcphjbkeiagm",
"fjoaledfpmneenckfbpdfhkmimnjocfa"
],
"ExtensionInstallBlacklist": [
"*"
]
}

View File

@ -0,0 +1,110 @@
{
"homepage": "http://www.google.com",
"homepage_is_newtabpage": false,
"first_run_tabs": [
"https://www.google.com/_/chrome/newtab?ie=UTF-8"
],
"custom_links": {
"initialized": true,
"list": [
{
"title": "YouTube",
"url": "https://www.youtube.com/"
},
{
"title": "Netflix",
"url": "https://netflix.com"
},
{
"title": "Hulu",
"url": "https://www.hulu.com/"
},
{
"title": "9Anime",
"url": "https://9anime.to/"
},
{
"title": "Crunchy Roll",
"url": "https://www.crunchyroll.com/"
},
{
"title": "Funimation",
"url": "https://www.funimation.com/"
},
{
"title": "Disney+",
"url": "https://www.disneyplus.com/"
},
{
"title": "HBO Now",
"url": "https://play.hbonow.com/"
},
{
"title": "Amazon Video",
"url": "https://www.amazon.com/Amazon-Video/b?node=2858778011"
},
{
"title": "VRV",
"url": "https://vrv.co/"
},
{
"title": "Twitch",
"url": "https://www.twitch.tv/"
},
{
"title": "Mixer",
"url": "https://mixer.com/"
}
]
},
"browser": {
"custom_chrome_frame": false,
"show_home_button": true,
"window_placement": {
"maximized": true
}
},
"bookmark_bar": {
"show_on_all_tabs": false
},
"sync_promo": {
"show_on_first_run_allowed": false
},
"distribution": {
"import_bookmarks_from_file": "bookmarks.html",
"import_bookmarks": true,
"import_history": true,
"import_home_page": true,
"import_search_engine": true,
"ping_delay": 60,
"do_not_create_desktop_shortcut": true,
"do_not_create_quick_launch_shortcut": true,
"do_not_create_taskbar_shortcut": true,
"do_not_launch_chrome": true,
"do_not_register_for_update_launch": true,
"make_chrome_default": true,
"make_chrome_default_for_user": true,
"system_level": false,
"verbose_logging": false
},
"profile": {
"avatar_index": 19,
"default_content_setting_values": {
"clipboard": 2,
"cookies": 4,
"geolocation": 2,
"media_stream_camera": 2,
"media_stream_mic": 2,
"midi_sysex": 2,
"payment_handler": 2,
"usb_guard": 2
},
"name": "neko",
"using_default_avatar": false,
"using_default_name": false,
"using_gaia_avatar": false
},
"signin": {
"allowed": false
}
}

View File

@ -0,0 +1,21 @@
[program:google-chrome]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/google-chrome --window-position=0,0 --display=%(ENV_DISPLAY)s --user-data-dir=/home/neko/.config/google-chrome --no-first-run --start-maximized --bwsi --force-dark-mode --disable-file-system --disable-gpu --disable-software-rasterizer --disable-dev-shm-usage
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/google-chrome.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -18,4 +18,4 @@ fi
docker run --rm -it \
-v "${PWD}/../server:/src" \
--entrypoint="go" \
neko_dev_server build -o "bin/neko" -i "cmd/neko/main.go"
neko_dev_server build -o "bin/neko" "cmd/neko/main.go"

View File

@ -0,0 +1,25 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
#
# install dependencies
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends openbox curl xz-utils file libgtk-3-0 libdbus-glib-1-2; \
#
# download TOR browser
DOWNLOAD_URI="$(curl -s -N https://www.torproject.org/download/ | grep -Po -m 1 '(?=(dist/torbrowser)).*(?<=.tar.xz)')"; \
echo "Downloading $DOWNLOAD_URI"; \
curl -sSL -o /tmp/tor.tar.xz "https://www.torproject.org/$DOWNLOAD_URI"; \
tar -xvJf /tmp/tor.tar.xz -C /opt; \
chown -R neko:neko /opt/tor-browser_en-US/; \
rm -f /tmp/tor.tar.xz; \
#
# clean up
apt-get --purge autoremove -y curl xz-utils; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*;
#
# copy configuation file
COPY supervisord.conf /etc/neko/supervisord/tor-browser.conf
COPY openbox.xml /etc/neko/openbox.xml

View File

@ -0,0 +1,763 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Default openbox config but all window decorations are moved
thereby making it harder to accidentally close the virtual browser -->
<openbox_config xmlns="http://openbox.org/3.4/rc"
xmlns:xi="http://www.w3.org/2001/XInclude">
<resistance>
<strength>10</strength>
<screen_edge_strength>20</screen_edge_strength>
</resistance>
<applications>
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
<application class="Tor*" name="Navigator">
<decor>no</decor>
<maximized>true</maximized>
<focus>yes</focus>
<layer>normal</layer>
</application>
</applications>
<focus>
<focusNew>yes</focusNew>
<!-- always try to focus new windows when they appear. other rules do
apply -->
<followMouse>no</followMouse>
<!-- move focus to a window when you move the mouse into it -->
<focusLast>yes</focusLast>
<!-- focus the last used window when changing desktops, instead of the one
under the mouse pointer. when followMouse is enabled -->
<underMouse>no</underMouse>
<!-- move focus under the mouse, even when the mouse is not moving -->
<focusDelay>200</focusDelay>
<!-- when followMouse is enabled, the mouse must be inside the window for
this many milliseconds (1000 = 1 sec) before moving focus to it -->
<raiseOnFocus>no</raiseOnFocus>
<!-- when followMouse is enabled, and a window is given focus by moving the
mouse into it, also raise the window -->
</focus>
<placement>
<policy>Smart</policy>
<!-- 'Smart' or 'UnderMouse' -->
<center>yes</center>
<!-- whether to place windows in the center of the free area found or
the top left corner -->
<monitor>Primary</monitor>
<!-- with Smart placement on a multi-monitor system, try to place new windows
on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
the active window is, 'Primary' - only on the primary monitor -->
<primaryMonitor>1</primaryMonitor>
<!-- The monitor where Openbox should place popup dialogs such as the
focus cycling popup, or the desktop switch popup. It can be an index
from 1, specifying a particular monitor. Or it can be one of the
following: 'Mouse' - where the mouse is, or
'Active' - where the active window is -->
</placement>
<theme>
<name>Clearlooks</name>
<titleLayout>NLIMC</titleLayout>
<!--
available characters are NDSLIMC, each can occur at most once.
N: window icon
L: window label (AKA title).
I: iconify
M: maximize
C: close
S: shade (roll up/down)
D: omnipresent (on all desktops).
-->
<keepBorder>yes</keepBorder>
<animateIconify>yes</animateIconify>
<font place="ActiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuHeader">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuItem">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="ActiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
</theme>
<desktops>
<!-- this stuff is only used at startup, pagers allow you to change them
during a session
these are default values to use when other ones are not already set
by other applications, or saved in your session
use obconf if you want to change these without having to log out
and back in -->
<number>1</number>
<firstdesk>1</firstdesk>
<names>
<!-- set names up here if you want to, like this:
<name>desktop 1</name>
<name>desktop 2</name>
-->
</names>
<popupTime>875</popupTime>
<!-- The number of milliseconds to show the popup for when switching
desktops. Set this to 0 to disable the popup. -->
</desktops>
<resize>
<drawContents>yes</drawContents>
<popupShow>Nonpixel</popupShow>
<!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
<popupPosition>Center</popupPosition>
<!-- 'Center', 'Top', or 'Fixed' -->
<popupFixedPosition>
<!-- these are used if popupPosition is set to 'Fixed' -->
<x>10</x>
<!-- positive number for distance from left edge, negative number for
distance from right edge, or 'Center' -->
<y>10</y>
<!-- positive number for distance from top edge, negative number for
distance from bottom edge, or 'Center' -->
</popupFixedPosition>
</resize>
<!-- You can reserve a portion of your screen where windows will not cover when
they are maximized, or when they are initially placed.
Many programs reserve space automatically, but you can use this in other
cases. -->
<margins>
<top>0</top>
<bottom>0</bottom>
<left>0</left>
<right>0</right>
</margins>
<dock>
<position>TopLeft</position>
<!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
<floatingX>0</floatingX>
<floatingY>0</floatingY>
<noStrut>no</noStrut>
<stacking>Above</stacking>
<!-- 'Above', 'Normal', or 'Below' -->
<direction>Vertical</direction>
<!-- 'Vertical' or 'Horizontal' -->
<autoHide>no</autoHide>
<hideDelay>300</hideDelay>
<!-- in milliseconds (1000 = 1 second) -->
<showDelay>300</showDelay>
<!-- in milliseconds (1000 = 1 second) -->
<moveButton>Middle</moveButton>
<!-- 'Left', 'Middle', 'Right' -->
</dock>
<keyboard>
<chainQuitKey>C-g</chainQuitKey>
<!-- Keybindings for desktop switching -->
<keybind key="C-A-Left">
<action name="GoToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Right">
<action name="GoToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Up">
<action name="GoToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="C-A-Down">
<action name="GoToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Left">
<action name="SendToDesktop"><to>left</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Right">
<action name="SendToDesktop"><to>right</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Up">
<action name="SendToDesktop"><to>up</to><wrap>no</wrap></action>
</keybind>
<keybind key="S-A-Down">
<action name="SendToDesktop"><to>down</to><wrap>no</wrap></action>
</keybind>
<keybind key="W-F1">
<action name="GoToDesktop"><to>1</to></action>
</keybind>
<keybind key="W-F2">
<action name="GoToDesktop"><to>2</to></action>
</keybind>
<keybind key="W-F3">
<action name="GoToDesktop"><to>3</to></action>
</keybind>
<keybind key="W-F4">
<action name="GoToDesktop"><to>4</to></action>
</keybind>
<keybind key="W-d">
<action name="ToggleShowDesktop"/>
</keybind>
<!-- Keybindings for windows -->
<keybind key="A-F4">
<action name="Close"/>
</keybind>
<keybind key="A-Escape">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</keybind>
<keybind key="A-space">
<!--action name="ShowMenu"><menu>client-menu</menu></action-->
</keybind>
<!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
<keybind key="A-Print">
<action name="Execute"><command>scrot -s</command></action>
</keybind>
<!-- Keybindings for window switching -->
<keybind key="A-Tab">
<action name="NextWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="A-S-Tab">
<action name="PreviousWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="C-A-Tab">
<action name="NextWindow">
<panels>yes</panels><desktop>yes</desktop>
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<!-- Keybindings for window switching with the arrow keys -->
<keybind key="W-S-Right">
<action name="DirectionalCycleWindows">
<direction>right</direction>
</action>
</keybind>
<keybind key="W-S-Left">
<action name="DirectionalCycleWindows">
<direction>left</direction>
</action>
</keybind>
<keybind key="W-S-Up">
<action name="DirectionalCycleWindows">
<direction>up</direction>
</action>
</keybind>
<keybind key="W-S-Down">
<action name="DirectionalCycleWindows">
<direction>down</direction>
</action>
</keybind>
<!-- Keybindings for running applications -->
<keybind key="W-e">
<action name="Execute">
<startupnotify>
<enabled>true</enabled>
<name>Konqueror</name>
</startupnotify>
<command>kfmclient openProfile filemanagement</command>
</action>
</keybind>
<!-- Launch scrot when Print is pressed -->
<keybind key="Print">
<action name="Execute"><command>scrot</command></action>
</keybind>
</keyboard>
<mouse>
<dragThreshold>1</dragThreshold>
<!-- number of pixels the mouse must move before a drag begins -->
<doubleClickTime>500</doubleClickTime>
<!-- in milliseconds (1000 = 1 second) -->
<screenEdgeWarpTime>400</screenEdgeWarpTime>
<!-- Time before changing desktops when the pointer touches the edge of the
screen while moving a window, in milliseconds (1000 = 1 second).
Set this to 0 to disable warping -->
<screenEdgeWarpMouse>false</screenEdgeWarpMouse>
<!-- Set this to TRUE to move the mouse pointer across the desktop when
switching due to hitting the edge of the screen -->
<context name="Frame">
<mousebind button="A-Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="A-Left" action="Click">
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="A-Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Right" action="Drag">
<action name="Resize"/>
</mousebind>
<mousebind button="A-Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-S-Up" action="Click">
<action name="SendToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-S-Down" action="Click">
<action name="SendToDesktop"><to>next</to></action>
</mousebind>
</context>
<context name="Titlebar">
<mousebind button="Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="Left" action="DoubleClick">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Up" action="Click">
<action name="if">
<shaded>no</shaded>
<then>
<action name="Shade"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
<action name="Lower"/>
</then>
</action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="if">
<shaded>yes</shaded>
<then>
<action name="Unshade"/>
<action name="Raise"/>
</then>
</action>
</mousebind>
</context>
<context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="Top">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>top</edge></action>
</mousebind>
</context>
<context name="Left">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>left</edge></action>
</mousebind>
</context>
<context name="Right">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>right</edge></action>
</mousebind>
</context>
<context name="Bottom">
<mousebind button="Left" action="Drag">
<action name="Resize"><edge>bottom</edge></action>
</mousebind>
<!--mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="TRCorner BRCorner TLCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Drag">
<action name="Resize"/>
</mousebind>
</context>
<context name="Client">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Icon">
<!--mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu"><menu>client-menu</menu></action>
</mousebind-->
</context>
<context name="AllDesktops">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleOmnipresent"/>
</mousebind>
</context>
<context name="Shade">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleShade"/>
</mousebind>
</context>
<context name="Iconify">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Iconify"/>
</mousebind>
</context>
<context name="Maximize">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Middle" action="Click">
<action name="ToggleMaximize"><direction>vertical</direction></action>
</mousebind>
<mousebind button="Right" action="Click">
<action name="ToggleMaximize"><direction>horizontal</direction></action>
</mousebind>
</context>
<context name="Close">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Close"/>
</mousebind>
</context>
<context name="Desktop">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Root">
<!-- Menus -->
<!--mousebind button="Middle" action="Press">
<action name="ShowMenu"><menu>client-list-combined-menu</menu></action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="ShowMenu"><menu>root-menu</menu></action>
</mousebind-->
</context>
<context name="MoveResize">
<mousebind button="Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop"><to>previous</to></action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop"><to>next</to></action>
</mousebind>
</context>
</mouse>
<menu>
<!-- You can specify more than one menu file in here and they are all loaded,
just don't make menu ids clash or, well, it'll be kind of pointless -->
<!-- default menu file (or custom one in $HOME/.config/openbox/) -->
<!-- system menu files on Debian systems -->
<!--file>/var/lib/openbox/debian-menu.xml</file-->
<file>menu.xml</file>
<hideDelay>200</hideDelay>
<!-- if a press-release lasts longer than this setting (in milliseconds), the
menu is hidden again -->
<middle>no</middle>
<!-- center submenus vertically about the parent entry -->
<submenuShowDelay>100</submenuShowDelay>
<!-- time to delay before showing a submenu after hovering over the parent
entry.
if this is a negative value, then the delay is infinite and the
submenu will not be shown until it is clicked on -->
<submenuHideDelay>400</submenuHideDelay>
<!-- time to delay before hiding a submenu when selecting another
entry in parent menu
if this is a negative value, then the delay is infinite and the
submenu will not be hidden until a different submenu is opened -->
<showIcons>yes</showIcons>
<!-- controls if icons appear in the client-list-(combined-)menu -->
<manageDesktops>yes</manageDesktops>
<!-- show the manage desktops section in the client-list-(combined-)menu -->
</menu>
<applications>
<!--
# this is an example with comments through out. use these to make your
# own rules, but without the comments of course.
# you may use one or more of the name/class/role/title/type rules to specify
# windows to match
<application name="the window's _OB_APP_NAME property (see obxprop)"
class="the window's _OB_APP_CLASS property (see obxprop)"
groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
role="the window's _OB_APP_ROLE property (see obxprop)"
title="the window's _OB_APP_TITLE property (see obxprop)"
type="the window's _OB_APP_TYPE property (see obxprob)..
(if unspecified, then it is 'dialog' for child windows)">
# you may set only one of name/class/role/title/type, or you may use more
# than one together to restrict your matches.
# the name, class, role, and title use simple wildcard matching such as those
# used by a shell. you can use * to match any characters and ? to match
# any single character.
# the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
# or desktop
# when multiple rules match a window, they will all be applied, in the
# order that they appear in this list
# each rule element can be left out or set to 'default' to specify to not
# change that attribute of the window
<decor>yes</decor>
# enable or disable window decorations
<shade>no</shade>
# make the window shaded when it appears, or not
<position force="no">
# the position is only used if both an x and y coordinate are provided
# (and not set to 'default')
# when force is "yes", then the window will be placed here even if it
# says you want it placed elsewhere. this is to override buggy
# applications who refuse to behave
<x>center</x>
# a number like 50, or 'center' to center on screen. use a negative number
# to start from the right (or bottom for <y>), ie -50 is 50 pixels from
# the right edge (or bottom). use 'default' to specify using value
# provided by the application, or chosen by openbox, instead.
<y>200</y>
<monitor>1</monitor>
# specifies the monitor in a xinerama setup.
# 1 is the first head, or 'mouse' for wherever the mouse is
</position>
<size>
# the size to make the window.
<width>20</width>
# a number like 20, or 'default' to use the size given by the application.
# you can use fractions such as 1/2 or percentages such as 75% in which
# case the value is relative to the size of the monitor that the window
# appears on.
<height>30%</height>
</size>
<focus>yes</focus>
# if the window should try be given focus when it appears. if this is set
# to yes it doesn't guarantee the window will be given focus. some
# restrictions may apply, but Openbox will try to
<desktop>1</desktop>
# 1 is the first desktop, 'all' for all desktops
<layer>normal</layer>
# 'above', 'normal', or 'below'
<iconic>no</iconic>
# make the window iconified when it appears, or not
<skip_pager>no</skip_pager>
# asks to not be shown in pagers
<skip_taskbar>no</skip_taskbar>
# asks to not be shown in taskbars. window cycling actions will also
# skip past such windows
<fullscreen>yes</fullscreen>
# make the window in fullscreen mode when it appears
<maximized>true</maximized>
# 'Horizontal', 'Vertical' or boolean (yes/no)
</application>
# end of the example
-->
</applications>
</openbox_config>

View File

@ -0,0 +1,24 @@
[program:tor-browser]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/opt/tor-browser_en-US/Browser/start-tor-browser --display=%(ENV_DISPLAY)s --setDefaultBrowser --window-size 1280,720
autorestart=true
priority=800
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/tor-browser.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true
stderr_logfile=/var/log/neko/tor-browser.err.log
stderr_logfile_maxbytes=100MB
stderr_logfile_backups=10
[program:openbox]
environment=HOME="/home/%(ENV_USER)s",USER="%(ENV_USER)s",DISPLAY="%(ENV_DISPLAY)s"
command=/usr/bin/openbox --config-file /etc/neko/openbox.xml
autorestart=true
priority=300
user=%(ENV_USER)s
stdout_logfile=/var/log/neko/openbox.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
redirect_stderr=true

View File

@ -1,13 +1,16 @@
ARG BASE_IMAGE=m1k1o/neko:base
FROM $BASE_IMAGE
ARG SRC_URL="https://github.com/macchrome/linchrome/releases/download/v89.0.4389.90-r843830-portable-ungoogled-Lin64/ungoogled-chromium_89.0.4389.90_1.vaapi_linux.tar.xz"
ARG API_URL="https://api.github.com/repos/macchrome/linchrome/releases/latest"
#
# install custom chromium build from woolyss with support for hevc/x265
RUN set -eux; apt-get update; \
apt-get install -y --no-install-recommends wget unzip libatk1.0-0 libatk-bridge2.0-0 libatomic1 \
libcups2 libgtk-3-0 libnss3 libpci3 libxcomposite1 libxss1 openbox xz-utils; \
libcups2 libgtk-3-0 libnss3 libpci3 libxcomposite1 libxss1 openbox xz-utils jq; \
#
# fetch latest release
SRC_URL="$(wget -O - "${API_URL}" 2>/dev/null | jq -r ".assets[] | .browser_download_url" | grep tar.xz)"; \
wget -O - /tmp/chromium.tar.xz "${SRC_URL}" | tar -xJf- -C /tmp; \
mv /tmp/ungoogled-chromium_* /usr/lib/chromium; \
#
@ -24,7 +27,7 @@ RUN set -eux; apt-get update; \
rm /tmp/widevine.zip; \
#
# clean up
apt-get --purge autoremove -y xz-utils; \
apt-get --purge autoremove -y xz-utils jq; \
apt-get clean -y; \
rm -rf /var/lib/apt/lists/* /var/cache/apt/*

View File

@ -1,4 +1,4 @@
{
"external_crx": "/usr/share/chromium/extensions/cjpalhdlnbpafiamejdnhcphjbkeiagm.crx",
"external_version": "1.30.6"
"external_version": "1.37.2"
}

View File

@ -1,4 +1,4 @@
{
"external_crx": "/usr/share/chromium/extensions/fjoaledfpmneenckfbpdfhkmimnjocfa.crx",
"external_version": "2.21.0"
"external_version": "2.31.0"
}

View File

@ -13,7 +13,7 @@
<applications>
<!-- Match all windows and remove their decorations (obxprop | grep "^_OB_APP") -->
<application class="vlc" name="vlc">
<application class="vlc" name="vlc" role="vlc-main">
<decor>no</decor>
<maximized>true</maximized>
<focus>yes</focus>

255
README.md
View File

@ -1,12 +1,18 @@
<div align="center">
<a href="https://n.eko.moe/#/" ><img src="https://raw.githubusercontent.com/nurdism/neko/master/docs/_media/logo.png" width="450" height="auto"/></a>
<a href="https://github.com/m1k1o/neko" title="Neko's Github repository.">
<img src="https://raw.githubusercontent.com/m1k1o/neko/master/docs/_media/logo.png" width="450" height="auto"/>
</a>
<p align="center">
<img src="https://img.shields.io/github/v/release/nurdism/neko" alt="release">
<img src="https://img.shields.io/github/license/nurdism/neko" alt="license">
<img src="https://img.shields.io/docker/pulls/nurdism/neko" alt="pulls">
<img src="https://img.shields.io/github/issues/nurdism/neko" alt="issues">
<a href="https://discord.gg/3U6hWpC" ><img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord"><a/>
<a href="https://github.com/nurdism/neko/actions" ><img src="https://github.com/nurdism/neko/workflows/deploy/badge.svg" alt="build"><a/>
<img src="https://img.shields.io/github/v/release/m1k1o/neko" alt="release">
<img src="https://img.shields.io/github/license/m1k1o/neko" alt="license">
<img src="https://img.shields.io/docker/pulls/m1k1o/neko" alt="pulls">
<img src="https://img.shields.io/github/issues/m1k1o/neko" alt="issues">
<a href="https://discord.gg/3U6hWpC">
<img src="https://discordapp.com/api/guilds/665851821906067466/widget.png" alt="Chat on discord">
</a>
<a href="https://github.com/m1k1o/neko/actions">
<img src="https://github.com/m1k1o/neko/actions/workflows/build.yml/badge.svg" alt="build">
</a>
</p>
<br/>
<br/>
@ -16,27 +22,36 @@
</div>
# n.eko (m1k1o fork)
This app uses Web RTC to stream a desktop inside of a docker container. This is fork of https://github.com/nurdism/neko.
This app uses Web-RTC to stream a desktop inside a docker container. This is a fork of https://github.com/nurdism/neko.
For n.eko room management software visit https://github.com/m1k1o/neko-rooms.
For n.eko room management software, visit https://github.com/m1k1o/neko-rooms.
## Differences to original repository.
### New Features
- Clipboard button with text area - for browsers, that don't support clipboard syncing (FireFox, what a shame...) or for HTTP.
- Keyboard modifier state synchronization (Num Lock, Caps Lock, Scroll Lock) for each hosting.
- Clipboard button with text area - for browsers, that don't support clipboard syncing (Firefox, what a shame...) or for HTTP.
- Keyboard modifier state synchronization (Num-Lock, Caps-Lock, Scroll-Lock) for each hosting.
- Added chromium ungoogled (with h265 support) an kept up to date (by @whalehub).
- Added Picture in Picture button (only for watching screen, controlling not possible).
- Added RTMP broadcast. Enables broadcasting neko screen to local RTMP server, YouTube or Twitch.
- Added RTMP broadcast. Enables broadcasting n.eko screen to local RTMP server, YouTube or Twitch. Example: `rtmp://a.rtmp.youtube.com/live2/<your-streaming-key>`.
- Stereo sound (works properly only in Firefox host).
- Added limited support for some mobile browsers with `playsinline` attribute.
- Added `VIDEO_BITRATE` and `AUDIO_BITRATE` in kbit/s to control stream quality (in collaboration with @mbattista).
- Added `MAX_FPS`, where you can specify max WebRTC frame rate. When set to `0`, frame rate won't be capped and you can enjoy your real `60fps` experience. Originally, it was constant at `25fps`.
- Invite links. You can invite people and they don't need to enter passwords by themselves (and get confused about user accounts that do not exits). You can put your password in URL using `?pwd=<your-password>` and it will be automatically used when logging in.
- Added `MAX_FPS`, where you can specify max WebRTC frame rate. When set to `0`, frame rate won't be capped, and you can enjoy your real `60fps` experience. Originally, it was constant at `25fps`.
- Invite links. You can invite people, and they don't need to enter passwords by themselves (and get confused about user accounts that do not exist). You can put your password in URL using `?pwd=<your-password>` and it will be automatically used when logging in.
- Added `/stats?pwd=<admin>` endpoint to get total active connections, host and members.
- Added `m1k1o/neko:vlc` tag, use VLC to watch local files together (by @mbattista).
- Added `m1k1o/neko:xfce` tag, as an non video related showcase (by @mbattista).
- Added `m1k1o/neko:xfce` tag, as a non-video related showcase (by @mbattista).
- Added ARM-based images, for Raspberry Pi support (by @mbattista).
- Added simple language picker.
- Added `?usr=<display-name>` that will prefill username. This allows creating auto-join links.
- Added `?cast=1` that will hide all control and show only video.
- Shake keyboard icon if someone attempted to control when is nobody hosting.
- Support for password protected `NEKO_ICESERVERS` (by @mbattista).
- Added bunch of translations (🇸🇰, 🇪🇸, 🇸🇪, 🇳🇴, 🇫🇷) by various people.
- Added `m1k1o/neko:google-chrome` tag.
- Show red dot badge on sidebar toggle if there are new messages, and user can't see them.
- Added `m1k1o/neko:brave` tag.
### Bugs
- Fixed minor gst pipeline bug.
@ -44,34 +59,52 @@ For n.eko room management software visit https://github.com/m1k1o/neko-rooms.
- Fixed h264 pipelines bugs (by @mbattista).
- Fixed sessions manager thread safety by adding mutexes (caused panic in rare edge cases).
- Now when user gets kicked, he won't join as a ghost user again but will be logged out.
- **iOS compatibility!** Fixed really strange CSS bug, which prevented iOS from loading the video.
- Proper disconnect only once with unsubscribing events. When webrtc fails, user won't be logged in without username again.
- **iOS compatibility!** Fixed a really strange CSS bug, which prevented iOS from loading the video.
- Proper disconnect only once with unsubscribing events. When Web-RTC fails, user won't be logged in without username again.
- Upgraded and fixed emojis to a new major version.
- Fixed bad `keymap -> keysym` translation to respect active modifiers (#45, with @mbattista).
- Respecting `NEKO_DEBUG` env variable.
- Full-screen support for iOS devices.
- Added `chrome-sandbox` to fix weird bug when chromium didn't start.
- Fixed keyboard mapping on macOS, when CMD could not be used for copy & paste.
- Fixed stop signal sent by supervisor to gracefully shut down neko server.
### Misc
- Custom docker workflow.
- Based on debian buster instead of stretch.
- Based on Debian buster instead of stretch.
- Versions bumped: Go 16, Node.js 14 (by @mbattista).
- Custom avatars without any 3rd party depenency.
- Custom avatars without any 3rd party dependency.
- Ignore duplicate notify bars.
- No pointer events for notify bars.
- Disable debug mode by default.
- Remove HTML tags from user name.
- Remove HTML tags from username.
- Upgraded `pion/webrtc` to v3 (by @mbattista).
- Added `requestFullscreen` compatibility for older browsers.
- Added `requestFullscreen` compatibility for older browsers and iOS devices.
- Fixed small lags in video and improved video UX (by @mbattista).
- Added `m1k1o/neko:vncviewer` tag, use `NEKO_VNC_URL` to specify VNC target and use n.eko as a bridge.
- Abiltiy to include neko as a component in another Vue.Js project (by @gbrian).
- Ability to include n.eko as a component in another Vue.Js project (by @gbrian).
- Added HEALTHCHECK to Dockerfile.
- Arguments in broadcast pipeline are optional, not positional and can be repeated `{url} {device} {display}`.
- Chat messages are dense, when repeated, they are joined together.
- While IP address fetching is now proxy ignored.
- Start unmuted on reconnects.
- Switched to the latest Firefox version instead of esr.
- Fixed very fast scroll speed on macOS.
- Broadcast pipeline errors are reported to the user.
- On stopping server all websocket connections are going to be gracefully disconnected.
# Getting started & FAQ
Use following docker images:
Use the following docker images:
- `m1k1o/neko:latest` - for Firefox.
- `m1k1o/neko:chromium` - for Chromium (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:google-chrome` - for Google Chrome (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:ungoogled-chromium` - for [Ungoogled Chromium](https://github.com/Eloston/ungoogled-chromium) (needs `--cap-add=SYS_ADMIN`) (by @whalehub).
- `m1k1o/neko:brave` - for [Brave Browser](https://brave.com) (needs `--cap-add=SYS_ADMIN`).
- `m1k1o/neko:tor-browser` - for Tor Browser.
- `m1k1o/neko:vncviewer` - for simple VNC viewer (specify `NEKO_VNC_URL` to your VNC target).
- `m1k1o/neko:vlc` - for VLC Video player (needs volume mounted to `/media` with local video files, or setting `VLC_MEDIA=/media` path).
- `m1k1o/neko:xfce` - for an shared desktop / installing shared software.
- `m1k1o/neko:xfce` - for a shared desktop / installing shared software.
- `m1k1o/neko:base` - for custom base.
For ARM-based devices (like Raspberry Pi, with GPU hardware acceleration):
@ -79,34 +112,157 @@ For ARM-based devices (like Raspberry Pi, with GPU hardware acceleration):
- `m1k1o/neko:arm-chromium` - for Chromium.
- `m1k1o/neko:arm-base` - for custom arm based.
Networking:
Images are built using GitHub actions on every push and on weekly basis to keep all browsers up-to-date,
### Networking:
- If you want to use n.eko in **external** network, you can omit `NEKO_NAT1TO1`. It will automatically get your Public IP.
- If you want to use n.eko in **internal** network, set `NEKO_NAT1TO1` to your local IP address (e.g. `NEKO_NAT1TO1: 192.168.1.20`)-
- Currently, it is not supported to supply multiple NAT addresses (see https://github.com/m1k1o/neko/issues/47).
Why so many ports?
- WebRTC needs UDP ports for each channel it creates towards users.
- Every user will need 2 UDP ports (for getting audio/video and sending mouse positions).
- You can freely limit number of UDP ports. But you can't map them to diferent ports.
- This **WONT** work: `32000-32100:52000-52100/udp`
### Why so many ports?
- WebRTC needs UDP ports in order to transfer Audio/Video towards user and Mouse/Keyboard events to the server in real time.
- If you don't set `NEKO_ICELITE=true`, every user will need 2 UDP ports.
- If you set `NEKO_ICELITE=true`, every user will need only 1 UDP port. It is **recommended** to use *ice-lite*.
- Do not forget, they are **UDP** ports, that configuration must be correct in your firewall/router/docker.
- You can freely limit number of UDP ports. But you can't map them to different ports.
- This **WON'T** work: `32000-32100:52000-52100/udp`
- You can change API port (8080).
- This **WILL** work: `3000:8080`
Behind reverse proxy?
- Nginx configuration: https://github.com/nurdism/neko/issues/111#issuecomment-742656957
- Apache configuration: https://github.com/nurdism/neko/blob/cad98a62a5bd7f1daf2c11980631bb14ba81a1f6/docs/apache-proxypass-config.md#example-apache-config
- Traefik configuration: https://github.com/m1k1o/neko-vpn/blob/a1b934515dcf597992a515d61d307c2450a11002/docker-compose.yml#L38-L43
### Behind reverse proxy?
Want to use VPN for your neko browsing?
<details>
<summary>Traefik2 configuration</summary>
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.services.neko-frontend.loadbalancer.server.port=8080"
- "traefik.http.routers.neko.rule=${TRAEFIK_RULE}"
- "traefik.http.routers.neko.entrypoints=${TRAEFIK_ENTRYPOINTS}"
- "traefik.http.routers.neko.tls.certresolver=${TRAEFIK_CERTRESOLVER}"
```
(by @m1k1o, [example](https://github.com/m1k1o/neko-vpn/blob/a1b934515dcf597992a515d61d307c2450a11002/docker-compose.yml#L38-L43))
</details>
<details>
<summary>Nginx configuration</summary>
```conf
server {
listen 443 ssl http2;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Protocol $scheme;
}
}
```
(by @GigaFyde, [source](https://github.com/nurdism/neko/issues/111#issuecomment-742656957))
</details>
<details>
<summary>Apache configuration</summary>
```xml
<VirtualHost *:80>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
# Paths of those modules might vary across different distros.
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
LoadModule proxy_wstunnel_module /usr/lib/apache2/modules/mod_proxy_wstunnel.so
ServerName example.com
ServerAlias www.example.com
ProxyRequests Off
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule /ws(.*) "ws://localhost:8080/ws$1" [P,L]
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
</VirtualHost>
```
(by @DarkReaper231, [source](https://github.com/nurdism/neko/blob/cad98a62a5bd7f1daf2c11980631bb14ba81a1f6/docs/apache-proxypass-config.md#example-apache-config))
</details>
<details>
<summary>Caddy configuration</summary>
```conf
https://example.com {
reverse_proxy localhost:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
```
(by @ccallahan, [source](https://github.com/nurdism/neko/pull/125/commits/eb4ceda75423b0d960c8aea0240acf6d7a10fef4))
</details>
### Want to customize and install own add-ons, set custom bookmarks?
- You would need to modify the existing policy file and mount it to your container.
- For Firefox, copy [this](https://github.com/m1k1o/neko/blob/dev/.m1k1o/firefox/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/usr/share/firefox-esr/distribution/policies.json'`
- For Chromium, copy [this](https://github.com/m1k1o/neko/blob/dev/.m1k1o/chromium/policies.json) file, modify and mount it as: ` -v '${PWD}/policies.json:/etc/chromium/policies/managed/policies.json'`
### Want to use VPN for your n.eko browsing?
- Check this out: https://github.com/m1k1o/neko-vpn
Accounts:
- There are no accounts, display name (a.k.a. username) can be freely chosen. Only paword needs to match. Depeding on which password matches, visitor gets its privilege:
### Want to have multiple rooms on demand?
- Check this out: https://github.com/m1k1o/neko-rooms
### Want to use different Apps than Browser?
- Check this out: https://github.com/m1k1o/neko-apps
### Accounts:
- There are no accounts, display name (a.k.a. username) can be freely chosen. Only password needs to match. Depending on which password matches, the visitor gets its privilege:
- Anyone, who enters with `NEKO_PASSWORD` will be **user**.
- Anyone, who enters with `NEKO_PASSWORD_ADMIN` will be **admin**.
Screen size
### Screen size
- Only admins can change screen size.
- You can set default screen size, but this size **MUST** be one from list, that your server supports.
- You can set a default screen size, but this size **MUST** be one from the list, that your server supports.
- You will get this list in frontend, where you can choose from.
## Firefox
@ -209,13 +365,17 @@ services:
! video/x-h264,profile=baseline,stream-format=byte-stream
```
## Not using docker?
You can execute `neko --help` to see available arguments. In [Dockerfile](https://github.com/m1k1o/neko/blob/dev/.m1k1o/base/Dockerfile) you can find required dependencies and install them manually.
## Mobile support
Neko is now working on iOS and Android! Also, the UI screens have been fixed for small screens.
N.eko is now working on iOS and Android! Also, the UI screens have been fixed for small screens.
![mobile-screens](https://i.imgur.com/K9gfscU.png)
## Docker-Compose Enviroment Options
## Docker-Compose Environment Options
```code
NEKO_SCREEN:
@ -270,9 +430,18 @@ NEKO_CERT:
NEKO_KEY:
- Path to the SSL-Certificate private key
- e.g. '/certs/key.pem'
NEKO_ICELITE:
- Use the ice lite protocol
- e.g. false
NEKO_ICESERVER:
- Describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer (simple usage for server without authentication)
- e.g. 'stun:stun.l.google.com:19302'
NEKO_ICESERVERS:
- Describes multiple STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer
- e.g. '[{"urls": ["turn:turn.example.com:19302", "stun:stun.example.com:19302"], "username": "name", "credential": "password"}, {"urls": ["stun:stun.example2.com:19302"]}]'
- [More information](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer)
```
# How to contribute?
Navigate to `.m1k1o/README.md` for further information.
Navigate to [.m1k1o/README.md](.m1k1o/README.md) for further information.

View File

@ -20,34 +20,33 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.14.0",
"@fortawesome/fontawesome-free": "^5.15.4",
"animejs": "^3.2.0",
"axios": "^0.21.1",
"date-fns": "^2.16.1",
"date-fns": "^2.23.0",
"emoji-datasource": "^6.0.1",
"emojilib": "^3.0.1",
"eventemitter3": "^4.0.7",
"resize-observer-polyfill": "^1.5.1",
"simple-markdown": "^0.7.2",
"sweetalert2": "^10.15.7",
"typed-vuex": "^0.1.21",
"v-tooltip": "^2.0.3",
"vue": "^2.6.12",
"vue": "^2.6.14",
"vue-class-component": "^7.2.6",
"vue-clickaway": "^2.2.2",
"vue-context": "^5.2.0",
"vue-i18n": "^8.21.1",
"vue-i18n": "^8.25.0",
"vue-notification": "^1.3.20",
"vue-property-decorator": "^9.1.2",
"vuex": "^3.5.1"
},
"devDependencies": {
"@types/animejs": "^3.1.2",
"@types/node": "^14.14.37",
"@types/animejs": "^3.1.4",
"@types/node": "^14.17.12",
"@types/vue": "^2.0.0",
"@types/vue-clickaway": "^2.2.0",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"@vue/cli-plugin-babel": "^4.5.6",
"@vue/cli-plugin-eslint": "^4.5.6",
"@vue/cli-plugin-typescript": "^4.5.6",
@ -55,15 +54,16 @@
"@vue/cli-service": "^4.5.12",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"core-js": "^3.9.1",
"core-js": "^3.16.4",
"emojilib": "^3.0.4",
"eslint": "^6.8.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-vue": "^7.8.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-vue": "^7.17.0",
"node-sass": "^5.0.0",
"prettier": "^2.1.2",
"prettier": "^2.3.2",
"sass-loader": "^10.1.1",
"ts-node": "^9.1.1",
"typescript": "^4.2.3",
"vue-template-compiler": "^2.6.12"
"typescript": "^4.4.2",
"vue-template-compiler": "^2.6.14"
}
}

File diff suppressed because one or more lines are too long

View File

@ -19,5 +19,8 @@
<strong>We're sorry but n.eko doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="neko"></div>
<p>
A self hosted virtual browser (<a href="https://github.com/m1k1o/neko">m1k1o/neko</a>) that runs in docker.
</p>
</body>
</html>

View File

@ -5,20 +5,20 @@
</template>
<template v-else>
<main class="neko-main">
<div class="header-container">
<div v-if="!hideControls" class="header-container">
<neko-header />
</div>
<div class="video-container">
<neko-video ref="video" />
<neko-video ref="video" :hideControls="hideControls" @control-attempt="controlAttempt" />
</div>
<div class="room-container">
<div v-if="!hideControls" class="room-container">
<neko-members />
<div class="room-menu">
<div class="settings">
<neko-menu />
</div>
<div class="controls">
<neko-controls />
<neko-controls :shakeKbd="shakeKbd" />
</div>
<div class="emotes">
<neko-emotes />
@ -26,10 +26,16 @@
</div>
</div>
</main>
<neko-side v-if="side" />
<neko-side v-if="!hideControls && side" />
<neko-connect v-if="!connected" />
<neko-about v-if="about" />
<notifications group="neko" position="top left" :ignoreDuplicates="true" style="top: 50px;pointer-events: none" />
<notifications
v-if="!hideControls"
group="neko"
position="top left"
style="top: 50px; pointer-events: none"
:ignoreDuplicates="true"
/>
</template>
</div>
</template>
@ -137,7 +143,7 @@
</style>
<script lang="ts">
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Vue, Component, Ref, Watch } from 'vue-property-decorator'
import Connect from '~/components/connect.vue'
import Video from '~/components/video.vue'
@ -168,6 +174,25 @@
export default class extends Vue {
@Ref('video') video!: Video
shakeKbd = false
get hideControls() {
return !!new URL(location.href).searchParams.get('cast')
}
@Watch('hideControls', { immediate: true })
onHideControls() {
this.$accessor.video.setMuted(false)
this.$accessor.settings.setSound(false)
}
controlAttempt() {
if (this.shakeKbd || this.$accessor.remote.hosted) return
this.shakeKbd = true
window.setTimeout(() => (this.shakeKbd = false), 5000)
}
get about() {
return this.$accessor.client.about
}

File diff suppressed because it is too large Load Diff

View File

@ -143,23 +143,27 @@
return this.$accessor.client.about_page
}
async Load() {
this.loading = true
try {
const res = await this.$http.get<string>('https://raw.githubusercontent.com/m1k1o/neko/dev/README.md')
const res2 = await this.$http.post('https://api.github.com/markdown', {
text: res.data,
mode: 'gfm',
context: 'github/gollum',
})
this.$accessor.client.setAbout(res2.data)
} catch (err: any) {
console.error(err)
} finally {
this.loading = false
}
}
mounted() {
if (this.about === '') {
this.loading = true
this.$http
.get<string>('https://raw.githubusercontent.com/nurdism/neko/master/docs/README.md')
.then((res) => {
return this.$http.post('https://api.github.com/markdown', {
text: res.data,
mode: 'gfm',
context: 'github/gollum',
})
})
.then((res) => {
this.$accessor.client.setAbout(res.data)
this.loading = false
})
.catch((err) => console.error(err))
this.Load()
}
}

View File

@ -2,7 +2,14 @@
<div class="chat">
<ul class="chat-history" ref="history" @click="onClick">
<template v-for="(message, index) in history">
<li :key="index" class="message" v-if="message.type === 'text'">
<li
:key="index"
class="message"
v-if="message.type === 'text'"
:class="{
bulk: index > 0 && history[index - 1].id == message.id && history[index - 1].type === 'text',
}"
>
<div class="author" @contextmenu.stop.prevent="onContext($event, { member: member(message.id) })">
<neko-avatar class="avatar" :seed="member(message.id).displayname" :size="40" />
</div>
@ -24,7 +31,7 @@
boundariesElement: 'body',
}"
>
<strong v-if="message.id === id">{{ $t('you') }}</strong>
<strong v-if="message.id === id && $te('you')">{{ $t('you') }}</strong>
<strong v-else>{{ member(message.id).displayname }}</strong>
{{ message.content }}
</div>
@ -94,6 +101,7 @@
word-wrap: break-word;
&.message {
padding-top: 15px;
font-size: 16px;
.author {
@ -104,7 +112,7 @@
height: 40px;
border-radius: 50%;
background: $style-primary;
margin: 0px 10px 10px 0px;
margin-right: 10px;
.avatar {
width: 100%;
@ -219,6 +227,19 @@
}
}
}
&.bulk {
padding-top: 0px;
.author {
visibility: hidden;
height: 0;
}
.content-head {
display: none;
}
}
}
&.event {

View File

@ -45,7 +45,7 @@
@Ref('textarea') readonly _textarea!: HTMLTextAreaElement
private opened: boolean = false
private typing: any = null
private typing?: number
get clipboard() {
return this.$accessor.remote.clipboard
@ -56,15 +56,16 @@
if (this.typing) {
clearTimeout(this.typing)
this.typing = undefined
}
this.typing = setTimeout(() => this.$accessor.remote.sendClipboard(this.clipboard), 500)
this.typing = window.setTimeout(() => this.$accessor.remote.sendClipboard(this.clipboard), 500)
}
open() {
this.opened = true
document.body.addEventListener('click', this.close)
setTimeout(() => this._textarea.focus(), 0)
window.setTimeout(() => this._textarea.focus(), 0)
}
close() {

View File

@ -1,7 +1,7 @@
<template>
<div class="connect">
<div class="window">
<div class="logo">
<div class="logo" title="About n.eko" @click.stop.prevent="about">
<img src="@/assets/images/logo.svg" alt="n.eko" />
<span><b>n</b>.eko</span>
</div>
@ -47,6 +47,7 @@
flex-direction: row;
justify-content: center;
align-items: center;
cursor: pointer;
img {
height: 90px;
@ -157,14 +158,23 @@
private password: string = ''
mounted() {
// auto-password fill
let password = this.$accessor.password
if (this.autoPassword !== null) {
this.removeUrlParam('pwd')
password = this.autoPassword
}
if (this.$accessor.displayname !== '' && password !== '') {
this.$accessor.login({ displayname: this.$accessor.displayname, password })
// auto-user fill
let displayname = this.$accessor.displayname
const usr = new URL(location.href).searchParams.get('usr')
if (usr) {
this.removeUrlParam('usr')
displayname = this.$accessor.displayname || usr
}
if (displayname !== '' && password !== '') {
this.$accessor.login({ displayname, password })
this.autoPassword = null
}
}
@ -194,23 +204,27 @@
}
}
async login() {
login() {
let password = this.password
if (this.autoPassword !== null) {
password = this.autoPassword
}
try {
await this.$accessor.login({ displayname: this.displayname, password })
this.autoPassword = null
} catch (err) {
if (this.displayname == '') {
this.$swal({
title: this.$t('connect.error') as string,
text: err.message,
text: this.$t('connect.empty_displayname') as string,
icon: 'error',
})
return
}
this.$accessor.login({ displayname: this.displayname, password })
this.autoPassword = null
}
about() {
this.$accessor.client.toggleAbout()
}
}
</script>

View File

@ -165,64 +165,64 @@
this.context.open(event, data)
}
kick(member: Member) {
this.$swal({
async kick(member: Member) {
const value = await this.$swal({
title: this.$t('context.confirm.kick_title', { name: member.displayname }) as string,
text: this.$t('context.confirm.kick_text', { name: member.displayname }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
}).then(({ value }) => {
if (value) {
this.$accessor.user.kick(member)
}
})
if (value) {
this.$accessor.user.kick(member)
}
}
ban(member: Member) {
this.$swal({
async ban(member: Member) {
const value = await this.$swal({
title: this.$t('context.confirm.ban_title', { name: member.displayname }) as string,
text: this.$t('context.confirm.ban_text', { name: member.displayname }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
}).then(({ value }) => {
if (value) {
this.$accessor.user.ban(member)
}
})
if (value) {
this.$accessor.user.ban(member)
}
}
mute(member: Member) {
this.$swal({
async mute(member: Member) {
const value = await this.$swal({
title: this.$t('context.confirm.mute_title', { name: member.displayname }) as string,
text: this.$t('context.confirm.mute_text', { name: member.displayname }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
}).then(({ value }) => {
if (value) {
this.$accessor.user.mute(member)
}
})
if (value) {
this.$accessor.user.mute(member)
}
}
unmute(member: Member) {
this.$swal({
async unmute(member: Member) {
const value = await this.$swal({
title: this.$t('context.confirm.unmute_title', { name: member.displayname }) as string,
text: this.$t('context.confirm.unmute_text', { name: member.displayname }) as string,
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.$t('context.confirm.button_yes') as string,
cancelButtonText: this.$t('context.confirm.button_cancel') as string,
}).then(({ value }) => {
if (value) {
this.$accessor.user.unmute(member)
}
})
if (value) {
this.$accessor.user.unmute(member)
}
}
adminRelease(member: Member) {

View File

@ -3,6 +3,7 @@
<li v-if="!isTouch">
<i
:class="[
shakeKbd ? 'shake' : '',
hosted && !hosting ? 'disabled' : '',
!hosted && !hosting ? 'faded' : '',
'fas',
@ -53,6 +54,46 @@
</template>
<style lang="scss" scoped>
.shake {
animation: shake 1.25s cubic-bezier(0, 0, 0, 1);
}
@keyframes shake {
0% {
transform: scale(1) translate(0px, 0) rotate(0);
}
10% {
transform: scale(1.25) translate(-2px, -2px) rotate(-20deg);
}
20% {
transform: scale(1.5) translate(4px, -4px) rotate(20deg);
}
30% {
transform: scale(1.75) translate(-4px, -6px) rotate(-20deg);
}
40% {
transform: scale(2) translate(6px, -8px) rotate(20deg);
}
50% {
transform: scale(2.25) translate(-6px, -10px) rotate(-20deg);
}
60% {
transform: scale(2) translate(6px, -8px) rotate(20deg);
}
70% {
transform: scale(1.75) translate(-4px, -6px) rotate(-20deg);
}
80% {
transform: scale(1.5) translate(4px, -4px) rotate(20deg);
}
90% {
transform: scale(1.25) translate(-2px, -2px) rotate(-20deg);
}
100% {
transform: scale(1) translate(0px, 0) rotate(0);
}
}
ul {
display: flex;
flex-direction: row;
@ -195,10 +236,12 @@
</style>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component({ name: 'neko-controls' })
export default class extends Vue {
@Prop(Boolean) readonly shakeKbd = false
get isTouch() {
return (
(typeof navigator.maxTouchPoints !== 'undefined' ? navigator.maxTouchPoints < 0 : false) ||

View File

@ -1,9 +1,9 @@
<template>
<div class="header">
<div class="neko">
<a href="https://github.com/m1k1o/neko" title="Github repository" target="_blank" class="neko">
<img src="@/assets/images/logo.svg" alt="n.eko" />
<span><b>n</b>.eko</span>
</div>
</a>
<ul class="menu">
<li>
<i
@ -15,8 +15,8 @@
? $t('room.unlock')
: $t('room.lock')
: locked
? $t('room.unlocked')
: $t('room.locked'),
? $t('room.locked')
: $t('room.unlocked'),
placement: 'bottom',
offset: 5,
boundariesElement: 'body',
@ -25,6 +25,7 @@
/>
</li>
<li>
<span v-if="showBadge" class="badge">&bull;</span>
<i class="fas fa-bars toggle" @click="toggleMenu" />
</li>
</ul>
@ -45,6 +46,8 @@
align-items: center;
width: 150px;
margin-left: 20px;
color: $text-normal;
text-decoration: none;
img {
display: block;
@ -94,6 +97,40 @@
.toggle {
background: $background-primary;
}
.badge {
position: absolute;
background: red;
font-weight: bold;
font-size: 1.25em;
border-radius: 50%;
width: 20px;
height: 20px;
text-align: center;
line-height: 20px;
pointer-events: none;
transform: translate(-50%, -25%) scale(1);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 1);
animation: badger-pulse 2s infinite;
}
@keyframes badger-pulse {
0% {
transform: translate(-50%, -25%) scale(0.85);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
}
70% {
transform: translate(-50%, -25%) scale(1);
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
}
100% {
transform: translate(-50%, -25%) scale(0.85);
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
}
}
}
}
}
@ -112,8 +149,22 @@
return this.$accessor.locked
}
get side() {
return this.$accessor.client.side
}
get texts() {
return this.$accessor.chat.texts
}
get showBadge() {
return !this.side && this.readTexts != this.texts
}
readTexts: number = 0
toggleMenu() {
this.$accessor.client.toggleSide()
this.readTexts = this.texts
}
toggleLock() {

View File

@ -1,21 +1,8 @@
import md, { SingleNodeParserRule, HtmlOutputRule, defaultRules, State, Rules } from 'simple-markdown'
import { Component, Watch, Vue, Prop } from 'vue-property-decorator'
const {
blockQuote,
inlineCode,
codeBlock,
autolink,
newline,
escape,
strong,
text,
link,
url,
em,
u,
br,
} = defaultRules
const { blockQuote, inlineCode, codeBlock, autolink, newline, escape, strong, text, link, url, em, u, br } =
defaultRules
type Rule = SingleNodeParserRule & HtmlOutputRule

View File

@ -13,6 +13,13 @@
v-if="admin"
/>
</li>
<li>
<select v-model="$i18n.locale">
<option v-for="(lang, i) in langs" :key="`Lang${i}`" :value="lang">
{{ lang }}
</option>
</select>
</li>
</ul>
</template>
@ -28,10 +35,33 @@
}
}
}
select {
appearance: none;
background-color: $background-tertiary;
border: 1px solid $background-primary;
color: white;
cursor: pointer;
border-radius: 5px;
height: 24px;
vertical-align: text-bottom;
display: inline-block;
option {
font-weight: normal;
color: $text-normal;
background-color: $background-tertiary;
}
&:hover {
border: 1px solid $background-primary;
}
}
</style>
<script lang="ts">
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { messages } from '~/locale'
@Component({ name: 'neko-menu' })
export default class extends Vue {
@ -39,6 +69,10 @@
return this.$accessor.user.admin
}
get langs() {
return Object.keys(messages)
}
about() {
this.$accessor.client.toggleAbout()
}

View File

@ -44,19 +44,23 @@
<span />
</label>
</li>
<template v-if="admin">
<li>
<span>{{ $t('setting.broadcast_is_active') }}</span>
<label class="switch">
<input type="checkbox" v-model="broadcast_is_active" />
<span />
</label>
</li>
<li>
<span>{{ $t('setting.broadcast_url') }}</span>
<input v-model="broadcast_url" :disabled="broadcast_is_active" class="input" />
</li>
</template>
<li class="broadcast" v-if="admin">
<div>
<span>{{ $t('setting.broadcast_title') }}</span>
<button v-if="!broadcast_is_active" @click.stop.prevent="$accessor.settings.broadcastCreate(broadcast_url)">
<i class="fas fa-play"></i>
</button>
<button v-else @click.stop.prevent="$accessor.settings.broadcastDestroy()" class="btn-red">
<i class="fas fa-stop"></i>
</button>
</div>
<input
v-model="broadcast_url"
:disabled="broadcast_is_active"
class="input"
placeholder="rtmp://a.rtmp.youtube.com/live2/<stream-key>"
/>
</li>
<li v-if="connected">
<button @click.stop.prevent="logout">{{ $t('logout') }}</button>
</li>
@ -266,6 +270,34 @@
background: none;
}
}
&.broadcast {
display: flex;
flex-direction: column;
div {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
button {
flex-shrink: 1;
width: auto !important;
margin: 0;
padding: 0 10px;
&.btn-red {
background: #a62626;
}
}
}
.input {
text-align: left;
width: auto !important;
margin: 0;
}
}
}
}
}
@ -338,14 +370,6 @@
return this.$accessor.settings.broadcast_is_active
}
set broadcast_is_active(value: boolean) {
if (value) {
this.$accessor.settings.broadcastCreate(this.broadcast_url)
} else {
this.$accessor.settings.broadcastDestroy()
}
}
get broadcast_url_remote() {
return this.$accessor.settings.broadcast_url
}

View File

@ -26,7 +26,7 @@
</div>
<div ref="aspect" class="player-aspect" />
</div>
<ul v-if="!fullscreen" class="video-menu top">
<ul v-if="!fullscreen && !hideControls" class="video-menu top">
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
<li v-if="admin"><i @click.stop.prevent="onResolution" class="fas fa-desktop"></i></li>
<li class="request-control">
@ -36,7 +36,7 @@
/>
</li>
</ul>
<ul v-if="!fullscreen" class="video-menu bottom">
<ul v-if="!fullscreen && !hideControls" class="video-menu bottom">
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
<i @click.stop.prevent="onClipboard" class="fas fa-clipboard"></i>
</li>
@ -183,7 +183,7 @@
</style>
<script lang="ts">
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { Component, Ref, Watch, Vue, Prop } from 'vue-property-decorator'
import ResizeObserver from 'resize-observer-polyfill'
import Emote from './emote.vue'
@ -193,6 +193,8 @@
// @ts-ignore
import GuacamoleKeyboard from '~/utils/guacamole-keyboard.ts'
const WHEEL_LINE_HEIGHT = 19
@Component({
name: 'neko-video',
components: {
@ -211,10 +213,13 @@
@Ref('resolution') readonly _resolution!: any
@Ref('clipboard') readonly _clipboard!: any
@Prop(Boolean) readonly hideControls = false
private keyboard = GuacamoleKeyboard()
private observer = new ResizeObserver(this.onResise.bind(this))
private focused = false
private fullscreen = false
private startsMuted = true
get admin() {
return this.$accessor.user.admin
@ -334,6 +339,7 @@
onMutedChanged(muted: boolean) {
if (this._video) {
this._video.muted = muted
this.startsMuted = muted
}
}
@ -383,7 +389,7 @@
this._video.addEventListener('canplaythrough', () => {
this.$accessor.video.setPlayable(true)
if (this.autoplay) {
if (!document.hasFocus() || !this.$accessor.active) {
if (this.startsMuted && (!document.hasFocus() || !this.$accessor.active)) {
this.$accessor.video.setMuted(true)
this._video.muted = true
}
@ -411,7 +417,7 @@
return true
}
this.$client.sendData('keydown', { key })
this.$client.sendData('keydown', { key: this.keyMap(key) })
return false
}
this.keyboard.onkeyup = (key: number) => {
@ -419,7 +425,7 @@
return
}
this.$client.sendData('keyup', { key })
this.$client.sendData('keyup', { key: this.keyMap(key) })
}
this.keyboard.listenTo(this._overlay)
}
@ -431,19 +437,60 @@
/* Guacamole Keyboard does not provide destroy functions */
}
play() {
get hasMacOSKbd() {
return /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)
}
KeyTable = {
XK_ISO_Level3_Shift: 0xfe03, // AltGr
XK_Mode_switch: 0xff7e, // Character set switch
XK_Control_L: 0xffe3, // Left control
XK_Control_R: 0xffe4, // Right control
XK_Meta_L: 0xffe7, // Left meta
XK_Meta_R: 0xffe8, // Right meta
XK_Alt_L: 0xffe9, // Left alt
XK_Alt_R: 0xffea, // Right alt
XK_Super_L: 0xffeb, // Left super
XK_Super_R: 0xffec, // Right super
}
keyMap(key: number): number {
// Alt behaves more like AltGraph on macOS, so shuffle the
// keys around a bit to make things more sane for the remote
// server. This method is used by noVNC, RealVNC and TigerVNC
// (and possibly others).
if (this.hasMacOSKbd) {
switch (key) {
case this.KeyTable.XK_Meta_L:
key = this.KeyTable.XK_Control_L
break
case this.KeyTable.XK_Super_L:
key = this.KeyTable.XK_Alt_L
break
case this.KeyTable.XK_Super_R:
key = this.KeyTable.XK_Super_L
break
case this.KeyTable.XK_Alt_L:
key = this.KeyTable.XK_Mode_switch
break
case this.KeyTable.XK_Alt_R:
key = this.KeyTable.XK_ISO_Level3_Shift
break
}
}
return key
}
async play() {
if (!this._video.paused || !this.playable) {
return
}
try {
this._video
.play()
.then(() => {
this.onResise()
})
.catch((err) => this.$log.error)
} catch (err) {
await this._video.play()
this.onResise()
} catch (err: any) {
this.$log.error(err)
}
}
@ -476,24 +523,40 @@
this.$accessor.remote.toggle()
}
requestFullscreen() {
if (typeof this._player.requestFullscreen === 'function') {
this._player.requestFullscreen()
_elementRequestFullscreen(el: HTMLElement) {
if (typeof el.requestFullscreen === 'function') {
el.requestFullscreen()
//@ts-ignore
} else if (typeof this._player.webkitRequestFullscreen === 'function') {
} else if (typeof el.webkitRequestFullscreen === 'function') {
//@ts-ignore
this._player.webkitRequestFullscreen()
el.webkitRequestFullscreen()
//@ts-ignore
} else if (typeof this._player.webkitEnterFullscreen === 'function') {
} else if (typeof el.webkitEnterFullscreen === 'function') {
//@ts-ignore
this._player.webkitEnterFullscreen()
el.webkitEnterFullscreen()
//@ts-ignore
} else if (typeof this._player.msRequestFullScreen === 'function') {
} else if (typeof el.msRequestFullScreen === 'function') {
//@ts-ignore
this._player.msRequestFullScreen()
el.msRequestFullScreen()
} else {
return false
}
this.onResise()
return true
}
requestFullscreen() {
// try to fullscreen player element
if (this._elementRequestFullscreen(this._player)) {
this.onResise()
return
}
// fallback to fullscreen video itself (on mobile devices)
if (this._elementRequestFullscreen(this._video)) {
this.onResise()
return
}
}
requestPictureInPicture() {
@ -502,21 +565,21 @@
this.onResise()
}
onFocus() {
async onFocus() {
if (!document.hasFocus() || !this.$accessor.active) {
return
}
if (this.hosting && this.clipboard_read_available) {
navigator.clipboard
.readText()
.then((text) => {
if (this.clipboard !== text) {
this.$accessor.remote.setClipboard(text)
this.$accessor.remote.sendClipboard(text)
}
})
.catch(this.$log.error)
try {
const text = await navigator.clipboard.readText()
if (this.clipboard !== text) {
this.$accessor.remote.setClipboard(text)
this.$accessor.remote.sendClipboard(text)
}
} catch (err: any) {
this.$log.error(err)
}
}
}
@ -529,6 +592,7 @@
})
}
wheelThrottle = false
onWheel(e: WheelEvent) {
if (!this.hosting || this.locked) {
return
@ -538,6 +602,16 @@
let x = e.deltaX
let y = e.deltaY
// Pixel units unless it's non-zero.
// Note that if deltamode is line or page won't matter since we aren't
// sending the mouse wheel delta to the server anyway.
// The difference between pixel and line can be important however since
// we have a threshold that can be smaller than the line height.
if (e.deltaMode !== 0) {
x *= WHEEL_LINE_HEIGHT
y *= WHEEL_LINE_HEIGHT
}
if (this.scroll_invert) {
x = x * -1
y = y * -1
@ -546,13 +620,25 @@
x = Math.min(Math.max(x, -this.scroll), this.scroll)
y = Math.min(Math.max(y, -this.scroll), this.scroll)
this.$client.sendData('wheel', { x, y })
if (!this.wheelThrottle) {
this.wheelThrottle = true
this.$client.sendData('wheel', { x, y })
window.setTimeout(() => {
this.wheelThrottle = false
}, 100)
}
}
onMouseDown(e: MouseEvent) {
if (!this.hosting) {
this.$emit('control-attempt', e)
}
if (!this.hosting || this.locked) {
return
}
this.onMousePos(e)
this.$client.sendData('mousedown', { key: e.button + 1 })
}
@ -561,6 +647,7 @@
if (!this.hosting || this.locked) {
return
}
this.onMousePos(e)
this.$client.sendData('mouseup', { key: e.button + 1 })
}

View File

@ -19,6 +19,19 @@ import Members from '~/components/members.vue'
import Emotes from '~/components/emotes.vue'
import About from '~/components/about.vue'
import Header from '~/components/header.vue'
import Chat from '~/components/chat.vue'
import Clipboard from '~/components/clipboard.vue'
import Emoji from '~/components/emoji.vue'
import Emote from '~/components/emote.vue'
import Context from '~/components/context.vue'
import Markdown from '~/components/markdown'
import Avatar from '~/components/avatar.vue'
// Vue
import Vue from 'vue'
import ToolTip from 'v-tooltip'
Vue.use(ToolTip)
const exportMixin = {
computed: {
@ -35,6 +48,7 @@ const plugini18n: PluginObject<undefined> = {
install(Vue) {
Vue.prototype.i18n = i18n
Vue.prototype.$t = i18n.t.bind(i18n)
Vue.prototype.$te = i18n.te.bind(i18n)
},
}
@ -49,17 +63,22 @@ function extend (component: any) {
.extend(exportMixin)
}
export const components = {
'neko-connect': extend(Connect),
'neko-video': extend(Video),
'neko-menu': extend(Menu),
'neko-side': extend(Side),
'neko-controls': extend(Controls),
'neko-members': extend(Members),
'neko-emotes': extend(Emotes),
'neko-about': extend(About),
'neko-header': extend(Header),
}
export const NekoConnect = extend(Connect)
export const NekoVideo = extend(Video)
export const NekoMenu = extend(Menu)
export const NekoSide = extend(Side)
export const NekoControls = extend(Controls)
export const NekoMembers = extend(Members)
export const NekoEmotes = extend(Emotes)
export const NekoAbout = extend(About)
export const NekoHeader = extend(Header)
export const NekoChat = extend(Chat)
export const NekoClipboard = extend(Clipboard)
export const NekoEmoji = extend(Emoji)
export const NekoEmote = extend(Emote)
export const NekoMarkdown = extend(Markdown)
export const NekoContext = extend(Context)
export const NekoAvatar = extend(Avatar)
neko.initialise()
export default neko

View File

@ -1,5 +1,5 @@
export const logout = 'logout'
export const unsupported = 'this browser does not support webrtc'
export const logout = 'log out'
export const unsupported = 'this web-browser does not support WebRTC'
export const admin_loggedin = 'You are logged in as an admin'
export const you = 'You'
export const send_a_message = 'Send a message'
@ -10,12 +10,13 @@ export const side = {
}
export const connect = {
login_title: 'Please Login',
login_title: 'Please Log In',
invitation_title: 'You have been invited to this room',
displayname: 'Enter your display name',
password: 'Password',
connect: 'Connect',
error: 'Login error',
empty_displayname: 'Display Name cannot be empty.',
}
export const context = {
@ -32,11 +33,11 @@ export const context = {
kick_title: 'Kick {name}?',
kick_text: 'Are you sure you want to kick {name}?',
ban_title: 'Ban {name}?',
ban_text: 'Are you sure you want to ban {name}? You will need to restart the server to undo this.',
ban_text: 'Do you want to ban {name}? You will need to restart the server to undo this.',
mute_title: 'Mute {name}?',
mute_text: 'Are you sure you want to mute {name}?',
unmute_title: 'Unmute {name}?',
unmute_text: 'Are you sure you want to unmute {name}?',
unmute_text: 'Do you want to unmute {name}?',
button_yes: 'Yes',
button_cancel: 'Cancel',
},
@ -63,29 +64,30 @@ export const setting = {
ignore_emotes: 'Ignore Emotes',
chat_sound: 'Play Chat Sound',
keyboard_layout: 'Keyboard Layout',
broadcast_is_active: 'Broadcast Enabled',
broadcast_url: 'RTMP url',
broadcast_title: 'Live Broadcast',
}
export const connection = {
logged_out: 'You have been logged out!',
connected: 'Successfully connected',
disconnected: 'You have been disconnected',
button_confirm: 'Ok',
logged_out: 'You have been logged out.',
reconnecting: 'Reconnecting...',
connected: 'Connected',
disconnected: 'Disconnected',
kicked: 'You have been removed from this room.',
button_confirm: 'OK',
}
export const notifications = {
connected: '{name} connected',
disconnected: '{name} disconnected',
controls_taken: '{name} took the controls',
controls_taken_force: 'force took the controls',
controls_taken_force: 'took the controls forcibly',
controls_taken_steal: 'took the controls from {name}',
controls_released: '{name} released the controls',
controls_released_force: 'force released the controls',
controls_released_force: 'released the controls forcibly',
controls_released_steal: 'released the controls from {name}',
controls_given: 'gave the controls to {name}',
controls_has: '{name} has the controls',
controls_has_alt: 'But I let them know you wanted it',
controls_has_alt: 'But I let the person know you wanted it',
controls_requesting: '{name} is requesting the controls',
resolution: 'changed the resolution to {width}x{height}@{rate}',
banned: 'banned {name}',

103
client/src/locale/es-sp.ts Normal file
View File

@ -0,0 +1,103 @@
export const logout = 'salir'
export const unsupported = 'este navegador no soporta webrtc'
export const admin_loggedin = 'Registrado como admin'
export const you = 'Tú'
export const send_a_message = 'Enviar un mensaje'
export const side = {
chat: 'Chat',
settings: 'Configuración',
}
export const connect = {
login_title: 'Por favor regístrate',
invitation_title: 'Te han invitado a esta sala',
displayname: 'Introduce tu nombre',
password: 'Contraseña',
connect: 'Conectar',
error: 'Error de login',
// TODO
//empty_displayname: 'Display Name cannot be empty.',
}
export const context = {
ignore: 'Ignorar',
unignore: 'No ignorar',
mute: 'Silenciar',
unmute: 'No silenciar',
release: 'Forzar liberar los controles',
take: 'Forzar obtener los controles',
give: 'Dar los controles',
kick: 'Echar',
ban: 'Bloquear IP',
confirm: {
kick_title: 'Echar a {name}?',
kick_text: 'Seguro que quiere echar a {name}?',
ban_title: 'Bloquear a {name}?',
ban_text: 'Seguroq ue quieres bloquear a {name}? Necesitarás reiniciar el servidor para deshacer esta acción.',
mute_title: 'Silenciar a {name}?',
mute_text: 'Seguro que quieres silenciar a {name}?',
unmute_title: 'Dejar de silenciar a {name}?',
unmute_text: 'Seguro que quieres dejar de silenciar a {name}?',
button_yes: 'Sí',
button_cancel: 'Cancelar',
},
}
export const controls = {
release: 'Controles liberador',
request: 'Controles solicitados',
lock: 'Controles bloqueados',
unlock: 'Controles desbloqueados',
}
export const room = {
lock: 'Bloquear sala (para usuarios)',
unlock: 'Desbloquear sala (para usuarios)',
locked: 'Sala bloqueada (para usuarios)',
unlocked: 'Sala desbloqueada (para usuarios)',
}
export const setting = {
scroll: 'Sensibilidad del Scroll',
scroll_invert: 'Invertir Scroll',
autoplay: 'Auto Reproducir Video',
ignore_emotes: 'Ignorar Emotes',
chat_sound: 'Reproducir Sonidos Chat',
keyboard_layout: 'Keyboard Layout',
// TODO
//broadcast_title: 'Live Broadcast',
}
export const connection = {
logged_out: 'Has salido!',
// TODO
//reconnecting: 'Reconnecting',
connected: 'Connectado correctamente',
disconnected: 'Has sido desconectado',
// TODO
//kicked: 'You have been removed from this room.',
button_confirm: 'De acuerdo',
}
export const notifications = {
connected: '{name} se ha conectado',
disconnected: '{name} se ha desconnectado',
controls_taken: '{name} tiene los controles',
controls_taken_force: 'controles confiscados',
controls_taken_steal: 'cogió los controles de {name}',
controls_released: '{name} ha liberado los controles',
controls_released_force: 'controles liberados',
controls_released_steal: 'controles liberados de {name}',
controls_given: 'controles asignados a {name}',
controls_has: '{name} tiene los controles',
controls_has_alt: 'Pero le diré que quieres los controles',
controls_requesting: '{name} quiere los controles',
resolution: 'resolución cambiada a {width}x{height}@{rate}',
banned: '{name} bloqueado',
kicked: '{name} expulsado',
muted: '{name} silenciado',
unmuted: '{name} no silenciado',
room_locked: 'bloqueó la sala',
room_unlocked: 'desbloqueó la sala',
}

103
client/src/locale/fr-fr.ts Normal file
View File

@ -0,0 +1,103 @@
export const logout = 'Se déconnecter'
export const unsupported = 'ce navigateur ne prend pas en charge WebRTC'
export const admin_loggedin = "Vous êtes connecté en tant qu'admin"
export const you = 'Vous'
export const send_a_message = 'Envoyer un message'
export const side = {
chat: 'Chat',
settings: 'Paramètres',
}
export const connect = {
login_title: 'Veuillez vous connecter',
invitation_title: 'Vous avez été invité dans cette salle',
displayname: "Entrez votre nom d'utilisateur",
password: 'Mot de passe',
connect: 'Connexion',
error: 'Erreur de connexion',
// TODO
//empty_displayname: 'Display Name cannot be empty.',
}
export const context = {
ignore: 'Ignorer',
unignore: 'Ne plus ignorer',
mute: 'Mute',
unmute: 'Démute',
release: 'Forcer le relachement de contrôle',
take: 'Forcer la prise de contrôle',
give: 'Donner le contrôle',
kick: 'Kicker',
ban: "Bannir l'IP",
confirm: {
kick_title: 'Kicker {name}?',
kick_text: 'Êtes vous sûr de kick {name}?',
ban_title: 'Bannir {name}?',
ban_text: 'Voulez-vous bannir {name}? Vous devez relancer le serveur pour annuler le bannissement.',
mute_title: 'Muter {name}?',
mute_text: 'Êtes-vous sûr de muter {name}?',
unmute_title: 'Démute {name}?',
unmute_text: 'Voulez-vous démuter {name}?',
button_yes: 'Oui',
button_cancel: 'Annuler',
},
}
export const controls = {
release: 'Relacher le contrôle',
request: 'Demander le contrôle',
lock: 'Vérouiller le contrôle',
unlock: 'Débloquer le contrôle',
}
export const room = {
lock: 'Vérouiller la salle (pour les utilisateurs)',
unlock: 'Dévérouiller la salle (pour les utilisateurs)',
locked: 'Salle vérouillée (pour les utilisateurs)',
unlocked: 'Salle dévérouillée (pour les utilisateurs)',
}
export const setting = {
scroll: 'Sensibilité de défilement (scroll)',
scroll_invert: 'Inverser le défilement (scroll)',
autoplay: 'Jouer automatiquement la vidéo',
ignore_emotes: 'Ignorer les Emotes',
chat_sound: 'Jouer le son du tchat',
keyboard_layout: 'Langue du clavier',
// TODO
//broadcast_title: 'Live Broadcast',
}
export const connection = {
logged_out: 'Vous avez été déconnecté.',
// TODO
//reconnecting: 'Reconnecting',
connected: 'Connecté',
disconnected: 'Déconnecté',
// TODO
//kicked: 'You have been removed from this room.',
button_confirm: 'OK',
}
export const notifications = {
connected: '{name} connecté',
disconnected: '{name} déconnecté',
controls_taken: '{name} a pris le contrôle',
controls_taken_force: 'a forcé la prise de contrôle',
controls_taken_steal: 'a pris le contrôle de {name}',
controls_released: '{name} a relâché le contrôle',
controls_released_force: 'a forcé la perte de contrôle',
controls_released_steal: 'a forcé la pêrte de contrôle de {name}',
controls_given: 'a donné le contrôle à {name}',
controls_has: '{name} a le contrôle',
controls_has_alt: "Mais j'ai fait savoir que vous le voulez",
controls_requesting: '{name} demande le contrôle',
resolution: 'a changé la résolution pour du {width}x{height}@{rate}',
banned: 'a banni {name}',
kicked: 'a kick {name}',
muted: 'a mute {name}',
unmuted: 'a démute {name}',
room_locked: 'a vérouillé la salle',
room_unlocked: 'a dévérouillé la salle',
}

View File

@ -1,5 +1,15 @@
import * as en from './en-us'
import * as es from './es-sp'
import * as sk from './sk-sk'
import * as sv from './sv-se'
import * as nb from './nb-no'
import * as fr from './fr-fr'
export const messages = {
en,
es,
sk,
sv,
nb,
fr,
}

103
client/src/locale/nb-no.ts Normal file
View File

@ -0,0 +1,103 @@
export const logout = 'logg ut'
export const unsupported = 'Denne nettleseren støtter ikke WebRTC'
export const admin_loggedin = 'Du er innlogget som administrator'
export const you = 'Deg'
export const send_a_message = 'Send en melding'
export const side = {
chat: 'Sludring',
settings: 'Innstillinger',
}
export const connect = {
login_title: 'Logg inn',
invitation_title: 'Du har blitt invitert til dette rommet',
displayname: 'Skriv inn ditt visningsnavn',
password: 'Passord',
connect: 'Koble til',
error: 'Innloggingsfeil',
// TODO
//empty_displayname: 'Display Name cannot be empty.',
}
export const context = {
ignore: 'Ignorer',
unignore: 'Opphev ignorering',
mute: 'Forstum',
unmute: 'Opphev forstummelse',
release: 'Slipp kontrollen med tvang',
take: 'Ta kontrollen med tvang',
give: 'Gi vekk kontroll',
kick: 'Kast ut',
ban: 'Bannlys IP',
confirm: {
kick_title: 'Kast ut {name}?',
kick_text: 'Vil du kaste ut {name}?',
ban_title: 'Bannlys {name}?',
ban_text: 'Vil du bannlyse {name}? Du vil måtte starte tjeneren på ny for å omgjøre dette.',
mute_title: 'Mute {name}?',
mute_text: 'Vil du forstumme {name}?',
unmute_title: 'Unmute {name}?',
unmute_text: 'Vil du oppheve forstummelsen av {name}?',
button_yes: 'Ja',
button_cancel: 'Avbryt',
},
}
export const controls = {
release: 'Slipp kontrollen',
request: 'Forespør kontroll',
lock: 'Lås kontrollen',
unlock: 'Lås opp kontrollen',
}
export const room = {
lock: 'Lås rommet (for brukere)',
unlock: 'Lås opp rommet (for brukere)',
locked: 'Rom låst (for brukere)',
unlocked: 'Rom opplåst (for brukere)',
}
export const setting = {
scroll: 'Rullingssensitivitet',
scroll_invert: 'Inverter rulling',
autoplay: 'Spill video automatisk',
ignore_emotes: 'Ignorer smilefjes',
chat_sound: 'Sludringslyd',
keyboard_layout: 'Tastaturoppsett',
// TODO
//broadcast_title: 'Live Broadcast',
}
export const connection = {
logged_out: 'Du har blitt utlogget.',
// TODO
//reconnecting: 'Reconnecting',
connected: 'Tilkoblet',
disconnected: 'Frakoblet',
// TODO
//kicked: 'You have been removed from this room.',
button_confirm: 'OK',
}
export const notifications = {
connected: '{name} koblet til',
disconnected: '{name} koblet fra',
controls_taken: '{name} tok kontrollen',
controls_taken_force: 'tok kontrollen med tvang',
controls_taken_steal: 'tok kontrollen fra {name}',
controls_released: '{name} ga vekk kontrollen',
controls_released_force: 'ga vekk kontrollen med tvang',
controls_released_steal: 'ga vekk kontrollen fra {name}',
controls_given: 'ga {name} kontrollen',
controls_has: '{name} har kontrollen',
controls_has_alt: 'Jeg la det komme vedkommende til kjenne at du ønsket den',
controls_requesting: '{name} forespør kontrollen',
resolution: 'endret oppløsningen til {width}x{height}@{rate}',
banned: 'bannlyste {name}',
kicked: 'kastet ut {name}',
muted: 'forstummet {name}',
unmuted: 'opphevet forstummingen av {name}',
room_locked: 'låste rommet',
room_unlocked: 'låste opp rommet',
}

100
client/src/locale/sk-sk.ts Normal file
View File

@ -0,0 +1,100 @@
export const logout = 'odhlásiť sa'
export const unsupported = 'tento prehliadač nepodporuje webrtc'
export const admin_loggedin = 'Ste prihlásení/á ako administrátor'
// export const you = '' // Incorrect in some translations! Cannot be used!
export const send_a_message = 'Odoslať správu'
export const side = {
chat: 'Chat',
settings: 'Nastavenia',
}
export const connect = {
login_title: 'Prihláste sa',
invitation_title: 'Boli ste pozvaný/á do miestnosti',
displayname: 'Vaše meno',
password: 'Heslo',
connect: 'Pripojiť sa',
error: 'Chyba pri prihlasovaní',
empty_displayname: 'Meno nemôže byť prázdne.',
}
export const context = {
ignore: 'Ignorovať',
unignore: 'Zrušiť ignorovanie',
mute: 'Zakázať chat',
unmute: 'Povoliť chat',
release: 'Zrušiť ovládanie',
take: 'Prevziať ovládanie',
give: 'Ponúknuť ovládanie',
kick: 'Kick',
ban: 'Ban IP',
confirm: {
kick_title: 'Kick {name}?',
kick_text: 'Ste si istý/á, že chcete vykopnúť používateľa {name}?',
ban_title: 'Ban {name}?',
ban_text:
'Ste si istý/á, že chcete zablokovať používateľa {name}? Pre odblokovanie budete musieť reštartovať server.',
mute_title: 'Zakázať chat pre používateľa {name}?',
mute_text: 'Ste si istý/á, že chcete zakázať chat pre používateľa {name}?',
unmute_title: 'Povoliť chat pre používateľa {name}?',
unmute_text: 'Ste si istý/á, že chcete povoliť chat pre používateľa {name}?',
button_yes: 'Áno',
button_cancel: 'Zrušiť',
},
}
export const controls = {
release: 'Uvoľniť ovládanie',
request: 'Požiadať o ovládanie',
lock: 'Zamknúť ovládanie',
unlock: 'Odomknúť ovládanie',
}
export const room = {
lock: 'Zamknúť miestnosť (pre používateľov)',
unlock: 'Odomknúť miestnosť (pre používateľov)',
locked: 'Miestnosť je zamknutá (pre používateľov)',
unlocked: 'Miestnosť odomknutá (pre používateľov)',
}
export const setting = {
scroll: 'Citlivosť kolieska myši',
scroll_invert: 'Invertovať koliesko myši',
autoplay: 'Automatické prehrávanie videa',
ignore_emotes: 'Ignorovať smajlíky',
chat_sound: 'Prehrávať zvuky chatu',
keyboard_layout: 'Rozloženie klávesnice',
broadcast_title: 'Živé vysielanie',
}
export const connection = {
logged_out: 'Boli ste odhlásený/á',
reconnecting: 'Obnova spojenia...',
connected: 'Úspešne pripojený/á',
disconnected: 'Boli ste odpojený/á',
kicked: 'Boli ste odstránený/á z tejto miestnosti.',
button_confirm: 'Ok',
}
export const notifications = {
connected: '{name} sa pripojil/a',
disconnected: '{name} sa odpojil/a',
controls_taken: '{name} prevzal/a ovládanie',
controls_taken_force: 'ovládanie bolo prevzaté',
controls_taken_steal: 'prevzal/a ovládanie od použivateľa {name}',
controls_released: '{name} uvoľnil/a ovládanie',
controls_released_force: 'ovládanie bolo uvoľnené',
controls_released_steal: 'uvoľnil/a ovládanie použivateľa {name}',
controls_given: 'ponúkol/a ovládanie používateľovi {name}',
controls_has: '{name} má ovládanie',
controls_has_alt: 'Ale dám mu vedieť, že si chcel ovládanie',
controls_requesting: '{name} by chcel/a ovládanie',
resolution: 'zmenené rozlíšenie na {width}x{height}@{rate}',
banned: '{name} dostal/a BAN',
kicked: '{name} bol/a vykopnutý/a',
muted: 'zakázal chat používateľovi {name}',
unmuted: 'povolil chat používateľovi {name}',
room_locked: 'miestnosť bola zamknutá',
room_unlocked: 'miestnosť bola odomknutá',
}

103
client/src/locale/sv-se.ts Normal file
View File

@ -0,0 +1,103 @@
export const logout = 'logga ut'
export const unsupported = 'denna webbläsare har inte stöd för webrtc'
export const admin_loggedin = 'Du är inloggad som en administratör'
export const you = 'Du'
export const send_a_message = 'Skicka ett meddelande'
export const side = {
chat: 'Chatt',
settings: 'Inställningar',
}
export const connect = {
login_title: 'Vänligen logga in',
invitation_title: 'Du har blivit inbjuden till detta rum',
displayname: 'Skriv in ditt namn',
password: 'Lösenord',
connect: 'Anslut',
error: 'Inloggningsfel',
// TODO
//empty_displayname: 'Display Name cannot be empty.',
}
export const context = {
ignore: 'Ignorera',
unignore: 'Inte ignorera',
mute: 'Tysta',
unmute: 'Ta bort tystning',
release: 'Tvinga ta bort kontrollen',
take: 'Tvinga ta kontrollen',
give: 'Ge kontrollen',
kick: 'Sparka',
ban: 'Bannlys IP',
confirm: {
kick_title: 'Sparka {name}?',
kick_text: 'Är du säker du vill sparka {name}?',
ban_title: 'Bannlys {name}?',
ban_text: 'Är du säker du vill bannlysa {name}? Du behöver starta om servern för att ta bort den bannlysningen.',
mute_title: 'Tysta {name}?',
mute_text: 'Är du säker du vill tysta {name}?',
unmute_title: 'Ta bort tystningen {name}?',
unmute_text: 'Är du säker du vill ta bort tystningen {name}?',
button_yes: 'Ja',
button_cancel: 'Avbryt',
},
}
export const controls = {
release: 'Ta kontrollen',
request: 'Fråga om kontroll',
lock: 'Lås kontrollen',
unlock: 'Lås upp kontrollen',
}
export const room = {
lock: 'Lås rum (för användare)',
unlock: 'Lås upp rummet (för användare)',
locked: 'Rum låst (för användare)',
unlocked: 'Rum upplåst (för användare)',
}
export const setting = {
scroll: 'Scrollkänslighet',
scroll_invert: 'Vänd Scrollen',
autoplay: 'Automatisk uppspelning av Video',
ignore_emotes: 'Ignorera Emotes',
chat_sound: 'Spela Chatt Ljud',
keyboard_layout: 'Tangentbordslayout',
// TODO
//broadcast_title: 'Live Broadcast',
}
export const connection = {
logged_out: 'Du har blivit utloggad!',
// TODO
//reconnecting: 'Reconnecting',
connected: 'Du har loggats in',
disconnected: 'Du har blivit frånkopplad',
// TODO
//kicked: 'You have been removed from this room.',
button_confirm: 'Ok',
}
export const notifications = {
connected: '{name} anslöt',
disconnected: '{name} kopplade ifrån',
controls_taken: '{name} tog kontrollen',
controls_taken_force: 'tvinga ta kontrollen',
controls_taken_steal: 'tog kontrollen från {name}',
controls_released: '{name} lämnade kontrollen',
controls_released_force: 'tvingade ta bort kontrollen',
controls_released_steal: 'tog bort kontrollen från {name}',
controls_given: 'gav kontrollen till {name}',
controls_has: '{name} har kontrollen',
controls_has_alt: 'Men jag låter dem veta att du vill ha den',
controls_requesting: '{name} frågar om kontrollen',
resolution: 'ändrade upplösningen till {width}x{height}@{rate}',
banned: 'bannlyste {name}',
kicked: 'sparkade {name}',
muted: 'tystade {name}',
unmuted: 'tog bort tystningen på {name}',
room_locked: 'låste rummet',
room_unlocked: 'låste upp rummet',
}

View File

@ -15,7 +15,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
protected _ws?: WebSocket
protected _peer?: RTCPeerConnection
protected _channel?: RTCDataChannel
protected _timeout?: NodeJS.Timeout
protected _timeout?: number
protected _displayname?: string
protected _state: RTCIceConnectionState = 'disconnected'
protected _id = ''
@ -52,10 +52,6 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
return
}
if (displayname === '') {
throw new Error('Display Name cannot be empty.')
}
this._displayname = displayname
this[EVENT.CONNECTING]()
@ -65,8 +61,8 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._ws.onmessage = this.onMessage.bind(this)
this._ws.onerror = (event) => this.onError.bind(this)
this._ws.onclose = (event) => this.onDisconnected.bind(this, new Error('websocket closed'))
this._timeout = setTimeout(this.onTimeout.bind(this), 15000)
} catch (err) {
this._timeout = window.setTimeout(this.onTimeout.bind(this), 15000)
} catch (err: any) {
this.onDisconnected(err)
}
}
@ -74,6 +70,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
protected disconnect() {
if (this._timeout) {
clearTimeout(this._timeout)
this._timeout = undefined
}
if (this._ws) {
@ -183,7 +180,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._ws!.send(JSON.stringify({ event, ...payload }))
}
public createPeer(sdp: string, lite: boolean, servers: string[]) {
public async createPeer(sdp: string, lite: boolean, servers: RTCIceServer[]) {
this.emit('debug', `creating peer`)
if (!this.socketOpen) {
this.emit(
@ -202,7 +199,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._peer = new RTCPeerConnection()
if (lite !== true) {
this._peer = new RTCPeerConnection({
iceServers: [{ urls: servers }],
iceServers: servers,
})
}
@ -223,16 +220,24 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
case 'checking':
if (this._timeout) {
clearTimeout(this._timeout)
this._timeout = undefined
}
break
case 'connected':
this.onConnected()
break
case 'disconnected':
this[EVENT.RECONNECTING]()
break
// https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Signaling_and_video_calling#ice_connection_state
// We don't watch the disconnected signaling state here as it can indicate temporary issues and may
// go back to a connected state after some time. Watching it would close the video call on any temporary
// network issue.
case 'failed':
this.onDisconnected(new Error('peer failed'))
break
case 'disconnected':
this.onDisconnected(new Error('peer disconnected'))
case 'closed':
this.onDisconnected(new Error('peer closed'))
break
}
}
@ -253,19 +258,20 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
}
this._candidates = []
this._peer
.createAnswer()
.then((d) => {
this._peer!.setLocalDescription(d)
this._ws!.send(
JSON.stringify({
event: EVENT.SIGNAL.ANSWER,
sdp: d.sdp,
displayname: this._displayname,
}),
)
})
.catch((err) => this.emit('error', err))
try {
const d = await this._peer.createAnswer()
this._peer!.setLocalDescription(d)
this._ws!.send(
JSON.stringify({
event: EVENT.SIGNAL.ANSWER,
sdp: d.sdp,
displayname: this._displayname,
}),
)
} catch (err: any) {
this.emit('error', err)
}
}
private onMessage(e: MessageEvent) {
@ -321,6 +327,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
private onConnected() {
if (this._timeout) {
clearTimeout(this._timeout)
this._timeout = undefined
}
if (!this.connected) {
@ -336,6 +343,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this.emit('debug', `connection timeout`)
if (this._timeout) {
clearTimeout(this._timeout)
this._timeout = undefined
}
this.onDisconnected(new Error('connection timeout'))
}
@ -350,6 +358,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this.emit('warn', `unhandled websocket event '${event}':`, payload)
}
protected abstract [EVENT.RECONNECTING](): void
protected abstract [EVENT.CONNECTING](): void
protected abstract [EVENT.CONNECTED](): void
protected abstract [EVENT.DISCONNECTED](reason?: Error): void

View File

@ -1,5 +1,6 @@
export const EVENT = {
// Internal Events
RECONNECTING: 'RECONNECTING',
CONNECTING: 'CONNECTING',
CONNECTED: 'CONNECTED',
DISCONNECTED: 'DISCONNECTED',
@ -10,6 +11,7 @@ export const EVENT = {
// Websocket Events
SYSTEM: {
DISCONNECT: 'system/disconnect',
ERROR: 'system/error',
},
SIGNAL: {
ANSWER: 'signal/answer',

View File

@ -6,7 +6,7 @@ import { EVENT } from './events'
import { accessor } from '~/store'
import {
DisconnectPayload,
SystemMessagePayload,
SignalProvidePayload,
MemberListPayload,
MemberDisconnectPayload,
@ -70,6 +70,16 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
/////////////////////////////
// Internal Events
/////////////////////////////
protected [EVENT.RECONNECTING]() {
this.$vue.$notify({
group: 'neko',
type: 'warning',
title: this.$vue.$t('connection.reconnecting') as string,
duration: 5000,
speed: 1000,
})
}
protected [EVENT.CONNECTING]() {
this.$accessor.setConnnecting()
}
@ -77,7 +87,11 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
protected [EVENT.CONNECTED]() {
this.$accessor.user.setMember(this.id)
this.$accessor.setConnected(true)
this.$accessor.setConnected(true)
this.$vue.$notify({
group: 'neko',
clean: true,
})
this.$vue.$notify({
group: 'neko',
@ -91,10 +105,6 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
protected [EVENT.DISCONNECTED](reason?: Error) {
this.cleanup()
if (reason && reason.message == 'kicked') {
this.$accessor.logout()
}
this.$vue.$notify({
group: 'neko',
type: 'error',
@ -120,8 +130,14 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
/////////////////////////////
// System Events
/////////////////////////////
protected [EVENT.SYSTEM.DISCONNECT]({ message }: DisconnectPayload) {
protected [EVENT.SYSTEM.DISCONNECT]({ message }: SystemMessagePayload) {
if (message == 'kicked') {
this.$accessor.logout()
message = this.$vue.$t('connection.kicked') as string
}
this.onDisconnected(new Error(message))
this.$vue.$swal({
title: this.$vue.$t('connection.disconnected'),
text: message,
@ -130,6 +146,15 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
})
}
protected [EVENT.SYSTEM.ERROR]({ title, message }: SystemMessagePayload) {
this.$vue.$swal({
title,
text: message,
icon: 'error',
confirmButtonText: this.$vue.$t('connection.button_confirm') as string,
})
}
/////////////////////////////
// Member Events
/////////////////////////////
@ -188,7 +213,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$vue.$notify({
group: 'neko',
type: 'info',
title: this.$vue.$t('notifications.controls_taken', { name: this.$vue.$t('you') }) as string,
title: this.$vue.$t('notifications.controls_taken', {
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
duration: 5000,
speed: 1000,
})
@ -213,7 +240,9 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$vue.$notify({
group: 'neko',
type: 'info',
title: this.$vue.$t('notifications.controls_released', { name: this.$vue.$t('you') }) as string,
title: this.$vue.$t('notifications.controls_released', {
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
duration: 5000,
speed: 1000,
})
@ -270,7 +299,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_given', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -361,7 +390,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.banned', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -381,7 +410,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.kicked', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -403,7 +432,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.muted', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -425,7 +454,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.unmuted', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -474,7 +503,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_taken_steal', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -501,7 +530,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_released_steal', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),
@ -524,7 +553,7 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newMessage({
id,
content: this.$vue.$t('notifications.controls_given', {
name: member.id == this.id ? this.$vue.$t('you') : member.displayname,
name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname,
}) as string,
type: 'event',
created: new Date(),

View File

@ -50,10 +50,12 @@ export interface WebSocketMessage {
SYSTEM MESSAGES/PAYLOADS
*/
// system/disconnect
export interface DisconnectMessage extends WebSocketMessage, DisconnectPayload {
event: typeof EVENT.SYSTEM.DISCONNECT
// system/error
export interface SystemMessage extends WebSocketMessage, SystemMessagePayload {
event: typeof EVENT.SYSTEM.DISCONNECT | typeof EVENT.SYSTEM.ERROR
}
export interface DisconnectPayload {
export interface SystemMessagePayload {
title: string
message: string
}
@ -67,7 +69,7 @@ export interface SignalProvideMessage extends WebSocketMessage, SignalProvidePay
export interface SignalProvidePayload {
id: string
lite: boolean
ice: string[]
ice: RTCIceServer[]
sdp: string
}

View File

@ -15,9 +15,7 @@ interface Anime {
path(
path: string | HTMLElement | SVGElement | null,
percent?: number,
): (
prop: string,
) => {
): (prop: string) => {
el: HTMLElement | SVGElement
property: string
totalLength: number

View File

@ -23,6 +23,7 @@ interface Message {
export const state = () => ({
history: [] as Message[],
emotes: {} as Emotes,
texts: 0,
})
export const getters = getterTree(state, {
@ -31,6 +32,10 @@ export const getters = getterTree(state, {
export const mutations = mutationTree(state, {
addMessage(state, message: Message) {
if (message.type == 'text') {
state.texts++
}
state.history = state.history.concat([message])
},
@ -52,6 +57,7 @@ export const mutations = mutationTree(state, {
reset(state) {
state.emotes = {}
state.history = []
state.texts = 0
},
})

View File

@ -58,17 +58,17 @@ export const mutations = mutationTree(state, {
export const actions = actionTree(
{ state, getters, mutations },
{
initialise() {
$http
.get<Emojis>('emoji.json')
.then((req) => {
for (const group of req.data.groups) {
accessor.emoji.addGroup(group)
}
accessor.emoji.setList(req.data.list)
accessor.emoji.setKeywords(req.data.keywords)
})
.catch(console.error)
async initialise() {
try {
const req = await $http.get<Emojis>('emoji.json')
for (const group of req.data.groups) {
accessor.emoji.addGroup(group)
}
accessor.emoji.setList(req.data.list)
accessor.emoji.setKeywords(req.data.keywords)
} catch (err: any) {
console.error(err)
}
},
},
)

View File

@ -81,7 +81,7 @@ export const actions = actionTree(
},
request({ getters }) {
if (!accessor.connected || !getters.hosting) {
if (!accessor.connected || getters.hosting) {
return
}
@ -89,7 +89,7 @@ export const actions = actionTree(
},
release({ getters }) {
if (!accessor.connected || getters.hosting) {
if (!accessor.connected || !getters.hosting) {
return
}

View File

@ -70,13 +70,13 @@ export const mutations = mutationTree(state, {
export const actions = actionTree(
{ state, getters, mutations },
{
initialise() {
$http
.get<KeyboardLayouts>('keyboard_layouts.json')
.then((req) => {
accessor.settings.setKeyboardLayoutsList(req.data)
})
.catch(console.error)
async initialise() {
try {
const req = await $http.get<KeyboardLayouts>('keyboard_layouts.json')
accessor.settings.setKeyboardLayoutsList(req.data)
} catch (err: any) {
console.error(err)
}
},
broadcastStatus({ getters }, { url, isActive }) {

View File

@ -2,7 +2,7 @@ import * as fs from 'fs'
import { custom } from './emoji_custom'
const datasource = require('emoji-datasource/emoji.json') as EmojiDatasource[]
const emojis = require('emojilib/emojis.json') as { [id: string]: Emoji }
const emojis = require('emojilib')
interface EmojiDatasource {
name: string
@ -43,14 +43,7 @@ interface EmojiDatasource {
obsoleted_by: string
}
interface Emoji {
keywords: string[]
char: string
fitzpatrick_scale: boolean
category: string
}
const SHEET_COLUMNS = 57
const SHEET_COLUMNS = 58
const MULTIPLY = 100 / (SHEET_COLUMNS - 1)
const css: string[] = []
@ -70,16 +63,6 @@ for (const emoji of custom) {
for (const source of datasource) {
const unified = source.unified.split('-').map(v => v.toLowerCase())
let emoji: Emoji | null = null
let emoji_id: string = ''
for (const id of Object.keys(emojis)) {
if (unified.includes(emojis[id].char.codePointAt(0)!.toString(16))) {
emoji_id = id
emoji = emojis[id]
break
}
}
if (!source.has_img_twitter) {
console.log(source.short_name, 'not avalible for set twitter')
continue
@ -87,10 +70,15 @@ for (const source of datasource) {
// keywords
let words: string[] = []
if (!emoji) {
for (const id of Object.keys(emojis)) {
if (unified.includes(id.codePointAt(0)!.toString(16))) {
words = [id, ...emojis[id]]
break
}
}
if (words.length == 0) {
console.log(source.short_name, 'no keywords')
} else {
words = [emoji_id, ...emoji.keywords]
}
for (const name of source.short_names) {

View File

@ -21,4 +21,7 @@ module.exports = {
},
},
},
devServer: {
disableHostCheck: true,
}
}

View File

@ -36,16 +36,11 @@ func init() {
zerolog.TimeFieldFormat = ""
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if viper.GetBool("debug") {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
console := zerolog.ConsoleWriter{Out: os.Stdout}
if !viper.GetBool("logs") {
log.Logger = log.Output(console)
} else {
logs := filepath.Join(".", "logs")
if runtime.GOOS == "linux" {
logs = "/var/log/neko"
@ -103,9 +98,14 @@ func init() {
}
}
debug := viper.GetBool("debug")
if debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
file := viper.ConfigFileUsed()
logger := log.With().
Bool("debug", viper.GetBool("debug")).
Bool("debug", debug).
Str("logging", viper.GetString("logs")).
Str("config", file).
Logger()

View File

@ -1,34 +1,56 @@
module n.eko.moe/neko
go 1.16
go 1.17
require (
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-chi/chi v4.1.2+incompatible
github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe // indirect
github.com/gopherjs/gopherjs v0.0.0-20210901121439-eee08aaf2717 // indirect
github.com/gorilla/websocket v1.4.2
github.com/kataras/go-events v0.0.2
github.com/kr/text v0.2.0 // indirect
github.com/pion/ice/v2 v2.1.12 // indirect
github.com/pion/interceptor v0.0.18
github.com/pion/logging v0.2.2
github.com/pion/rtp v1.7.2 // indirect
github.com/pion/srtp/v2 v2.0.5 // indirect
github.com/pion/webrtc/v3 v3.0.32
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.25.0
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210908191846-a5e095526f91 // indirect
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pion/interceptor v0.0.12
github.com/pion/logging v0.2.2
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pion/datachannel v1.4.21 // indirect
github.com/pion/dtls/v2 v2.0.9 // indirect
github.com/pion/mdns v0.0.5 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.7 // indirect
github.com/pion/sctp v1.7.12 // indirect
github.com/pion/sdp/v3 v3.0.4 // indirect
github.com/pion/stun v0.3.5 // indirect
github.com/pion/transport v0.12.3 // indirect
github.com/pion/turn/v2 v2.0.5 // indirect
github.com/pion/udp v0.1.1 // indirect
github.com/pion/webrtc/v3 v3.0.19
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.21.0
github.com/smartystreets/assertions v1.2.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.1.3
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210326220855-61e056675ecf // indirect
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181 // indirect
golang.org/x/text v0.3.5 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/ini.v1 v1.63.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

View File

@ -5,17 +5,44 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@ -23,12 +50,21 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -37,56 +73,107 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe h1:rcf1P0fm+1l0EjG16p06mYLj9gW9X36KgdHJ/88hS4g=
github.com/gopherjs/gopherjs v0.0.0-20210202160940-bed99a852dfe/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20210901121439-eee08aaf2717 h1:V1j4G8AXIJeyzT3ng2Oh4IRo/VEgRWYAsyYwhOz5rko=
github.com/gopherjs/gopherjs v0.0.0-20210901121439-eee08aaf2717/go.mod h1:0RnbP5ioI0nqRf3R9iK3iQaUJgsn0htlZEHCMn8FSfw=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -109,17 +196,22 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kataras/go-events v0.0.2 h1:fhyUPXvUbrjIPmH4vRdrAAGoNzdcwJPQmjhg47m1nMU=
github.com/kataras/go-events v0.0.2/go.mod h1:6IxMW59VJdEIqj3bjFGJvGLRdb0WHtrlxPZy9qXctcg=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@ -148,61 +240,71 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pion/datachannel v1.4.21 h1:3ZvhNyfmxsAqltQrApLPQMhSFNA+aT87RqyCq4OXmf0=
github.com/pion/datachannel v1.4.21/go.mod h1:oiNyP4gHx2DIwRzX/MFyH0Rz/Gz05OgBlayAI2hAWjg=
github.com/pion/dtls/v2 v2.0.8 h1:reGe8rNIMfO/UAeFLqO61tl64t154Qfkr4U3Gzu1tsg=
github.com/pion/dtls/v2 v2.0.8/go.mod h1:QuDII+8FVvk9Dp5t5vYIMTo7hh7uBkra+8QIm7QGm10=
github.com/pion/ice/v2 v2.0.16 h1:K6bzD8ef9vMKbGMTHaUweHXEyuNGnvr2zdqKoLKZPn0=
github.com/pion/ice/v2 v2.0.16/go.mod h1:SJNJzC27gDZoOW0UoxIoC8Hf2PDxG28hQyNdSexDu38=
github.com/pion/interceptor v0.0.12 h1:eC1iVneBIAQJEfaNAfDqAncJWhMDAnaXPRCJsltdokE=
github.com/pion/interceptor v0.0.12/go.mod h1:qzeuWuD/ZXvPqOnxNcnhWfkCZ2e1kwwslicyyPnhoK4=
github.com/pion/dtls/v2 v2.0.9 h1:7Ow+V++YSZQMYzggI0P9vLJz/hUFcffsfGMfT/Qy+u8=
github.com/pion/dtls/v2 v2.0.9/go.mod h1:O0Wr7si/Zj5/EBFlDzDd6UtVxx25CE1r7XM7BQKYQho=
github.com/pion/ice/v2 v2.1.10/go.mod h1:kV4EODVD5ux2z8XncbLHIOtcXKtYXVgLVCeVqnpoeP0=
github.com/pion/ice/v2 v2.1.12 h1:ZDBuZz+fEI7iDifZCYFVzI4p0Foy0YhdSSZ87ZtRcRE=
github.com/pion/ice/v2 v2.1.12/go.mod h1:ovgYHUmwYLlRvcCLI67PnQ5YGe+upXZbGgllBDG/ktU=
github.com/pion/interceptor v0.0.13/go.mod h1:svsW2QoLHLoGLUr4pDoSopGBEWk8FZwlfxId/OKRKzo=
github.com/pion/interceptor v0.0.18 h1:4I4JUcvD4Efrs39AI+MEkoQ6V3RldIjh4Gqu8rcUzho=
github.com/pion/interceptor v0.0.18/go.mod h1:LNvW3RHxHSGDCXWlofLo0eEtnWinHANEDMl4xKdjfjY=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw=
github.com/pion/mdns v0.0.5/go.mod h1:UgssrvdD3mxpi8tMxAXbsppL3vJ4Jipw1mTCW+al01g=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.6 h1:1zvwBbyd0TeEuuWftrd/4d++m+/kZSeiguxU61LFWpo=
github.com/pion/rtcp v1.2.6/go.mod h1:52rMNPWFsjr39z9B9MhnkqhPLoeHTv1aN63o/42bWE0=
github.com/pion/rtp v1.6.2 h1:iGBerLX6JiDjB9NXuaPzHyxHFG9JsIEdgwTC0lp5n/U=
github.com/pion/rtcp v1.2.7 h1:cPeOJu9sHMTLTWmxzLH8/wcF8giondpLgvXDPfauUBY=
github.com/pion/rtcp v1.2.7/go.mod h1:qVPhiCzAm4D/rxb6XzKeyZiQK69yJpbUDJSF7TgrqNo=
github.com/pion/rtp v1.6.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.6.5/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.0/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/rtp v1.7.2 h1:HCDKDCixh7PVjkQTsqHAbk1lg+bx059EHxcnyl42dYs=
github.com/pion/rtp v1.7.2/go.mod h1:bDb5n+BFZxXx0Ea7E5qe+klMuqiBrP+w8XSjiWtCUko=
github.com/pion/sctp v1.7.10/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.11/go.mod h1:EhpTUQu1/lcK3xI+eriS6/96fWetHGCvBi9MSsnaBN0=
github.com/pion/sctp v1.7.12 h1:GsatLufywVruXbZZT1CKg+Jr8ZTkwiPnmUC/oO9+uuY=
github.com/pion/sctp v1.7.12/go.mod h1:xFe9cLMZ5Vj6eOzpyiKjT9SwGM4KpK/8Jbw5//jc+0s=
github.com/pion/sdp/v3 v3.0.4 h1:2Kf+dgrzJflNCSw3TV5v2VLeI0s/qkzy2r5jlR0wzf8=
github.com/pion/sdp/v3 v3.0.4/go.mod h1:bNiSknmJE0HYBprTHXKPQ3+JjacTv5uap92ueJZKsRk=
github.com/pion/srtp/v2 v2.0.2 h1:664iGzVmaY7KYS5M0gleY0DscRo9ReDfTxQrq4UgGoU=
github.com/pion/srtp/v2 v2.0.2/go.mod h1:VEyLv4CuxrwGY8cxM+Ng3bmVy8ckz/1t6A0q/msKOw0=
github.com/pion/srtp/v2 v2.0.5 h1:ks3wcTvIUE/GHndO3FAvROQ9opy0uLELpwHJaQ1yqhQ=
github.com/pion/srtp/v2 v2.0.5/go.mod h1:8k6AJlal740mrZ6WYxc4Dg6qDqqhxoRG2GSjlUhDF0A=
github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg=
github.com/pion/stun v0.3.5/go.mod h1:gDMim+47EeEtfWogA37n6qXZS88L5V6LqFcf+DZA2UA=
github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
github.com/pion/transport v0.10.1/go.mod h1:PBis1stIILMiis0PewDw91WJeLJkyIMcEk+DwKOzf4A=
github.com/pion/transport v0.12.2/go.mod h1:N3+vZQD9HlDP5GWkZ85LohxNsDcNgofQmyL6ojX5d8Q=
github.com/pion/transport v0.12.3 h1:vdBfvfU/0Wq8kd2yhUMSDB/x+O4Z9MYVl2fJ5BT4JZw=
github.com/pion/transport v0.12.3/go.mod h1:OViWW9SP2peE/HbwBvARicmAVnesphkNkCVZIWJ6q9A=
github.com/pion/turn/v2 v2.0.5 h1:iwMHqDfPEDEOFzwWKT56eFmh6DYC6o/+xnLAEzgISbA=
github.com/pion/turn/v2 v2.0.5/go.mod h1:APg43CFyt/14Uy7heYUOGWdkem/Wu4PhCO/bjyrTqMw=
github.com/pion/udp v0.1.0/go.mod h1:BPELIjbwE9PRbd/zxI/KYBnbo7B6+oA6YuEaNE8lths=
github.com/pion/udp v0.1.1 h1:8UAPvyqmsxK8oOjloDk4wUt63TzFe9WEJkg5lChlj7o=
github.com/pion/udp v0.1.1/go.mod h1:6AFo+CMdKQm7UiA0eUPA8/eVCTx8jBIITLZHc9DWX5M=
github.com/pion/webrtc/v3 v3.0.19 h1:h3EOMuMNYkJ0X2w1iKNGGBLFN7M/Max0vNUF9IUXqBc=
github.com/pion/webrtc/v3 v3.0.19/go.mod h1:P/aoizAjeMUh61uAH58BRypn97IKjcLtIAm/mHqovJw=
github.com/pion/webrtc/v3 v3.0.32 h1:5J+zNep9am8Swh6kEMp+LaGXNvn6qQWpGkLBnVW44L4=
github.com/pion/webrtc/v3 v3.0.32/go.mod h1:wX3V5dQQUGCifhT1mYftC2kCrDQX6ZJ3B7Yad0R9JK0=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -215,21 +317,26 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.25.0 h1:Rj7XygbUHKUlDPcVdoLyR91fJBsduXj5fRxyqIQj/II=
github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
@ -242,10 +349,12 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -253,8 +362,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -268,13 +377,28 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -283,14 +407,19 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -300,11 +429,22 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -320,26 +460,61 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201201195509-5d6afe98e0b7/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210326220855-61e056675ecf h1:WUcCxqQqDT0aXO4VnQbfMvp4zh7m1Gb2clVuHUAGGRE=
golang.org/x/net v0.0.0-20210326220855-61e056675ecf/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210908191846-a5e095526f91 h1:E8wdt+zBjoxD3MA65wEc3pl25BsTi7tbkpwc4ANThjc=
golang.org/x/net v0.0.0-20210908191846-a5e095526f91/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -354,29 +529,59 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181 h1:64ChN/hjER/taL4YJuA+gpLfIMT+/NFherRZixbxOhg=
golang.org/x/sys v0.0.0-20210326220804-49726bf1d181/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 h1:xrCZDmdtoloIiooiA9q0OQb9r8HejIHYoHGhGCe1pGg=
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -387,6 +592,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@ -394,8 +600,42 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -406,10 +646,30 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -419,15 +679,70 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -437,14 +752,17 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.63.0 h1:2t0h8NA59dpVQpa5Yh8cIcR6nHAeBIEk0zlLVqfw4N4=
gopkg.in/ini.v1 v1.63.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@ -454,5 +772,10 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -1,6 +1,8 @@
package broadcast
import (
"sync"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
@ -9,6 +11,7 @@ import (
)
type BroadcastManager struct {
mu sync.Mutex
logger zerolog.Logger
pipeline *gst.Pipeline
remote *config.Remote
@ -27,9 +30,9 @@ func New(remote *config.Remote, config *config.Broadcast) *BroadcastManager {
}
}
func (manager *BroadcastManager) Start() {
func (manager *BroadcastManager) Start() error {
if !manager.enabled || manager.IsActive() {
return
return nil
}
var err error
@ -40,18 +43,19 @@ func (manager *BroadcastManager) Start() {
manager.url,
)
if err != nil {
manager.pipeline = nil
return err
}
manager.logger.Info().
Str("audio_device", manager.remote.Device).
Str("video_display", manager.remote.Display).
Str("rtmp_pipeline_src", manager.pipeline.Src).
Msgf("RTMP pipeline is starting...")
if err != nil {
manager.logger.Panic().Err(err).Msg("unable to create rtmp pipeline")
return
}
manager.pipeline.Play()
return nil
}
func (manager *BroadcastManager) Stop() {
@ -67,13 +71,25 @@ func (manager *BroadcastManager) IsActive() bool {
return manager.pipeline != nil
}
func (manager *BroadcastManager) Create(url string) {
func (manager *BroadcastManager) Create(url string) error {
manager.mu.Lock()
defer manager.mu.Unlock()
manager.url = url
manager.enabled = true
manager.Start()
err := manager.Start()
if err != nil {
manager.enabled = false
}
return err
}
func (manager *BroadcastManager) Destroy() {
manager.mu.Lock()
defer manager.mu.Unlock()
manager.Stop()
manager.enabled = false
}

View File

@ -63,9 +63,8 @@ GstFlowReturn gstreamer_send_new_sample_handler(GstElement *object, gpointer use
return GST_FLOW_OK;
}
GstElement *gstreamer_send_create_pipeline(char *pipeline) {
GError *error = NULL;
return gst_parse_launch(pipeline, &error);
GstElement *gstreamer_send_create_pipeline(char *pipeline, GError **error) {
return gst_parse_launch(pipeline, error);
}
void gstreamer_send_start_pipeline(GstElement *pipeline, int pipelineId) {

View File

@ -9,6 +9,7 @@ package gst
import "C"
import (
"fmt"
"strings"
"sync"
"time"
"unsafe"
@ -38,11 +39,10 @@ import (
// Pipeline is a wrapper for a GStreamer Pipeline
type Pipeline struct {
Pipeline *C.GstElement
Sample chan types.Sample
CodecName string
Src string
id int
Pipeline *C.GstElement
Sample chan types.Sample
Src string
id int
}
var pipelines = make(map[int]*Pipeline)
@ -66,12 +66,17 @@ func CreateRTMPPipeline(pipelineDevice string, pipelineDisplay string, pipelineS
var pipelineStr string
if pipelineSrc != "" {
pipelineStr = fmt.Sprintf(pipelineSrc, pipelineRTMP, pipelineDevice, pipelineDisplay)
// replace RTMP url
pipelineStr = strings.Replace(pipelineSrc, "{url}", pipelineRTMP, -1)
// replace audio device
pipelineStr = strings.Replace(pipelineStr, "{device}", pipelineDevice, -1)
// replace display
pipelineStr = strings.Replace(pipelineStr, "{display}", pipelineDisplay, -1)
} else {
pipelineStr = fmt.Sprintf("flvmux name=mux ! rtmpsink location='%s live=1' %s audio/x-raw,channels=2 ! audioconvert ! voaacenc ! mux. %s x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! mux.", pipelineRTMP, audio, video)
}
return CreatePipeline(pipelineStr, "")
return CreatePipeline(pipelineStr)
}
// CreateAppPipeline creates a GStreamer Pipeline
@ -192,23 +197,29 @@ func CreateAppPipeline(codecName string, pipelineDevice string, pipelineSrc stri
return nil, fmt.Errorf("unknown codec %s", codecName)
}
return CreatePipeline(pipelineStr, codecName)
return CreatePipeline(pipelineStr)
}
// CreatePipeline creates a GStreamer Pipeline
func CreatePipeline(pipelineStr string, codecName string) (*Pipeline, error) {
func CreatePipeline(pipelineStr string) (*Pipeline, error) {
pipelineStrUnsafe := C.CString(pipelineStr)
defer C.free(unsafe.Pointer(pipelineStrUnsafe))
pipelinesLock.Lock()
defer pipelinesLock.Unlock()
var err *C.GError
gstPipeline := C.gstreamer_send_create_pipeline(pipelineStrUnsafe, &err)
if err != nil {
defer C.g_error_free(err)
return nil, fmt.Errorf("%s", C.GoString(err.message))
}
p := &Pipeline{
Pipeline: C.gstreamer_send_create_pipeline(pipelineStrUnsafe),
Sample: make(chan types.Sample),
CodecName: codecName,
Src: pipelineStr,
id: len(pipelines),
Pipeline: gstPipeline,
Sample: make(chan types.Sample),
Src: pipelineStr,
id: len(pipelines),
}
pipelines[p.id] = p

View File

@ -8,7 +8,7 @@
extern void goHandlePipelineBuffer(void *buffer, int bufferLen, int samples, int pipelineId);
GstElement *gstreamer_send_create_pipeline(char *pipeline);
GstElement *gstreamer_send_create_pipeline(char *pipeline, GError **error);
void gstreamer_send_start_pipeline(GstElement *pipeline, int pipelineId);
void gstreamer_send_play_pipeline(GstElement *pipeline);

View File

@ -1,102 +1,102 @@
package endpoint
import (
"encoding/json"
"fmt"
"net/http"
"runtime/debug"
"encoding/json"
"fmt"
"net/http"
"runtime/debug"
"github.com/go-chi/chi/middleware"
"github.com/rs/zerolog/log"
"github.com/go-chi/chi/middleware"
"github.com/rs/zerolog/log"
)
type (
Endpoint func(http.ResponseWriter, *http.Request) error
Endpoint func(http.ResponseWriter, *http.Request) error
ErrResponse struct {
Status int `json:"status,omitempty"`
Err string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
Details string `json:"details,omitempty"`
Code string `json:"code,omitempty"`
RequestID string `json:"request,omitempty"`
}
ErrResponse struct {
Status int `json:"status,omitempty"`
Err string `json:"error,omitempty"`
Message string `json:"message,omitempty"`
Details string `json:"details,omitempty"`
Code string `json:"code,omitempty"`
RequestID string `json:"request,omitempty"`
}
)
func Handle(handler Endpoint) http.HandlerFunc {
fn := func(w http.ResponseWriter, r *http.Request) {
if err := handler(w, r); err != nil {
WriteError(w, r, err)
}
}
fn := func(w http.ResponseWriter, r *http.Request) {
if err := handler(w, r); err != nil {
WriteError(w, r, err)
}
}
return http.HandlerFunc(fn)
return http.HandlerFunc(fn)
}
var nonErrorsCodes = map[int]bool{
404: true,
404: true,
}
func errResponse(input interface{}) *ErrResponse {
var res *ErrResponse
var err interface{}
var res *ErrResponse
var err interface{}
switch input.(type) {
case *HandlerError:
e := input.(*HandlerError)
res = &ErrResponse{
Status: e.Status,
Err: http.StatusText(e.Status),
Message: e.Message,
}
err = e.Err
default:
res = &ErrResponse{
Status: http.StatusInternalServerError,
Err: http.StatusText(http.StatusInternalServerError),
}
err = input
}
switch input.(type) {
case *HandlerError:
e := input.(*HandlerError)
res = &ErrResponse{
Status: e.Status,
Err: http.StatusText(e.Status),
Message: e.Message,
}
err = e.Err
default:
res = &ErrResponse{
Status: http.StatusInternalServerError,
Err: http.StatusText(http.StatusInternalServerError),
}
err = input
}
if err != nil {
switch err.(type) {
case *error:
e := err.(error)
res.Details = e.Error()
break
default:
res.Details = fmt.Sprintf("%+v", err)
break
}
}
if err != nil {
switch err.(type) {
case *error:
e := err.(error)
res.Details = e.Error()
break
default:
res.Details = fmt.Sprintf("%+v", err)
break
}
}
return res
return res
}
func WriteError(w http.ResponseWriter, r *http.Request, err interface{}) {
hlog := log.With().
Str("module", "http").
Logger()
hlog := log.With().
Str("module", "http").
Logger()
res := errResponse(err)
res := errResponse(err)
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
res.RequestID = reqID
}
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
res.RequestID = reqID
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(res.Status)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(res.Status)
if err := json.NewEncoder(w).Encode(res); err != nil {
hlog.Warn().Err(err).Msg("Failed writing json error response")
}
if err := json.NewEncoder(w).Encode(res); err != nil {
hlog.Warn().Err(err).Msg("Failed writing json error response")
}
if !nonErrorsCodes[res.Status] {
logEntry := middleware.GetLogEntry(r)
if logEntry != nil {
logEntry.Panic(err, debug.Stack())
} else {
hlog.Error().Str("stack", string(debug.Stack())).Msgf("%+v", err)
}
}
if !nonErrorsCodes[res.Status] {
logEntry := middleware.GetLogEntry(r)
if logEntry != nil {
logEntry.Panic(err, debug.Stack())
} else {
hlog.Error().Str("stack", string(debug.Stack())).Msgf("%+v", err)
}
}
}

View File

@ -3,15 +3,15 @@ package endpoint
import "fmt"
type HandlerError struct {
Status int
Message string
Err error
Status int
Message string
Err error
}
func (e *HandlerError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %s", e.Message, e.Err.Error())
}
if e.Err != nil {
return fmt.Sprintf("%s: %s", e.Message, e.Err.Error())
}
return e.Message
return e.Message
}

View File

@ -13,6 +13,12 @@ func Logger(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
req := map[string]interface{}{}
// ignore healthcheck
if r.RequestURI == "/health" {
next.ServeHTTP(w, r)
return
}
if reqID := middleware.GetReqID(r.Context()); reqID != "" {
req["id"] = reqID
}

View File

@ -4,9 +4,9 @@ package middleware
// a pointer so it fits in an interface{} without allocation. This technique
// for defining context keys was copied from Go 1.7's new use of context in net/http.
type ctxKey struct {
name string
name string
}
func (k *ctxKey) String() string {
return "neko/ctx/" + k.name
return "neko/ctx/" + k.name
}

View File

@ -4,21 +4,21 @@ package middleware
// https://github.com/zenazn/goji/tree/master/web/middleware
import (
"net/http"
"net/http"
"n.eko.moe/neko/internal/http/endpoint"
"n.eko.moe/neko/internal/http/endpoint"
)
func Recoverer(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rvr := recover(); rvr != nil {
endpoint.WriteError(w, r, rvr)
}
}()
fn := func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rvr := recover(); rvr != nil {
endpoint.WriteError(w, r, rvr)
}
}()
next.ServeHTTP(w, r)
}
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
return http.HandlerFunc(fn)
}

View File

@ -1,14 +1,14 @@
package middleware
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"os"
"strings"
"sync/atomic"
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"net/http"
"os"
"strings"
"sync/atomic"
)
// Key to use when setting the request ID.
@ -37,19 +37,19 @@ var reqid uint64
// than a millionth of a percent chance of generating two colliding IDs.
func init() {
hostname, err := os.Hostname()
if hostname == "" || err != nil {
hostname = "localhost"
}
var buf [12]byte
var b64 string
for len(b64) < 10 {
rand.Read(buf[:])
b64 = base64.StdEncoding.EncodeToString(buf[:])
b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
}
hostname, err := os.Hostname()
if hostname == "" || err != nil {
hostname = "localhost"
}
var buf [12]byte
var b64 string
for len(b64) < 10 {
rand.Read(buf[:])
b64 = base64.StdEncoding.EncodeToString(buf[:])
b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
}
prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10])
prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10])
}
// RequestID is a middleware that injects a request ID into the context of each
@ -58,32 +58,32 @@ func init() {
// process, and where the last number is an atomically incremented request
// counter.
func RequestID(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestID := r.Header.Get("X-Request-Id")
if requestID == "" {
myid := atomic.AddUint64(&reqid, 1)
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
}
ctx = context.WithValue(ctx, RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
fn := func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
requestID := r.Header.Get("X-Request-Id")
if requestID == "" {
myid := atomic.AddUint64(&reqid, 1)
requestID = fmt.Sprintf("%s-%06d", prefix, myid)
}
ctx = context.WithValue(ctx, RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
}
// GetReqID returns a request ID from the given context if one is present.
// Returns the empty string if a request ID cannot be found.
func GetReqID(ctx context.Context) string {
if ctx == nil {
return ""
}
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
return reqID
}
return ""
if ctx == nil {
return ""
}
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
return reqID
}
return ""
}
// NextRequestID generates the next request ID in the sequence.
func NextRequestID() uint64 {
return atomic.AddUint64(&reqid, 1)
return atomic.AddUint64(&reqid, 1)
}

View File

@ -2,6 +2,7 @@ package remote
import (
"fmt"
"os/exec"
"time"
"github.com/kataras/go-events"
@ -55,7 +56,9 @@ func (manager *RemoteManager) Start() {
}
manager.createPipelines()
manager.broadcast.Start()
if err := manager.broadcast.Start(); err != nil {
manager.logger.Panic().Err(err).Msg("unable to create rtmp pipeline")
}
go func() {
defer func() {
@ -170,7 +173,9 @@ func (manager *RemoteManager) ChangeResolution(width int, height int, rate int)
defer func() {
manager.video.Start()
manager.broadcast.Start()
if err := manager.broadcast.Start(); err != nil {
manager.logger.Panic().Err(err).Msg("unable to create rtmp pipeline")
}
manager.logger.Info().Msg("starting video pipeline...")
}()
@ -244,7 +249,7 @@ func (manager *RemoteManager) GetScreenSize() *types.ScreenSize {
}
func (manager *RemoteManager) SetKeyboardLayout(layout string) {
xorg.SetKeyboardLayout(layout)
exec.Command("setxkbmap", layout).Run()
}
func (manager *RemoteManager) SetKeyboardModifiers(NumLock int, CapsLock int, ScrollLock int) {

View File

@ -43,7 +43,7 @@ func (manager *SessionManager) New(id string, admin bool, socket types.WebSocket
manager.mu.Lock()
manager.members[id] = session
if manager.remote.Streaming() != true && len(manager.members) > 0 {
if !manager.remote.Streaming() && len(manager.members) > 0 {
manager.remote.StartStream()
}
manager.mu.Unlock()
@ -148,7 +148,7 @@ func (manager *SessionManager) Destroy(id string) error {
err := session.destroy()
delete(manager.members, id)
if manager.remote.Streaming() != false && len(manager.members) <= 0 {
if manager.remote.Streaming() && len(manager.members) <= 0 {
manager.remote.StopStream()
}
manager.mu.Unlock()

View File

@ -1,8 +1,6 @@
package session
import (
"sync"
"github.com/rs/zerolog"
"n.eko.moe/neko/internal/types"
"n.eko.moe/neko/internal/types/event"
@ -19,7 +17,6 @@ type Session struct {
manager *SessionManager
socket types.WebSocket
peer types.Peer
mu sync.Mutex
}
func (session *Session) ID() string {
@ -89,7 +86,7 @@ func (session *Session) Kick(reason string) error {
if session.socket == nil {
return nil
}
if err := session.socket.Send(&message.Disconnect{
if err := session.socket.Send(&message.SystemMessage{
Event: event.SYSTEM_DISCONNECT,
Message: reason,
}); err != nil {
@ -126,8 +123,8 @@ func (session *Session) SignalCandidate(data string) error {
}
return session.socket.Send(&message.SignalCandidate{
Event: event.SIGNAL_CANDIDATE,
Data: data,
});
Data: data,
})
}
func (session *Session) destroy() error {

View File

@ -1,10 +1,10 @@
package types
type BroadcastManager interface {
Start()
Start() error
Stop()
IsActive() bool
Create(url string)
Create(url string) error
Destroy()
GetUrl() string
}

View File

@ -10,7 +10,7 @@ type Broadcast struct {
}
func (Broadcast) Init(cmd *cobra.Command) error {
cmd.PersistentFlags().String("broadcast_pipeline", "", "audio codec parameters to use for broadcasting")
cmd.PersistentFlags().String("broadcast_pipeline", "", "custom gst pipeline used for broadcasting, strings {url} {device} {display} will be replaced")
if err := viper.BindPFlag("broadcast_pipeline", cmd.PersistentFlags().Lookup("broadcast_pipeline")); err != nil {
return err
}

View File

@ -1,17 +1,21 @@
package config
import (
"encoding/json"
"strconv"
"strings"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"n.eko.moe/neko/internal/utils"
"github.com/pion/webrtc/v3"
)
type WebRTC struct {
ICELite bool
ICEServers []string
ICEServers []webrtc.ICEServer
EphemeralMin uint16
EphemeralMax uint16
NAT1To1IPs []string
@ -28,6 +32,11 @@ func (WebRTC) Init(cmd *cobra.Command) error {
return err
}
cmd.PersistentFlags().String("ipfetch", "http://checkip.amazonaws.com", "automatically fetch IP address from given URL when nat1to1 is not present")
if err := viper.BindPFlag("ipfetch", cmd.PersistentFlags().Lookup("ipfetch")); err != nil {
return err
}
cmd.PersistentFlags().Bool("icelite", false, "configures whether or not the ice agent should be a lite agent")
if err := viper.BindPFlag("icelite", cmd.PersistentFlags().Lookup("icelite")); err != nil {
return err
@ -38,19 +47,39 @@ func (WebRTC) Init(cmd *cobra.Command) error {
return err
}
cmd.PersistentFlags().String("iceservers", "", "describes a single STUN and TURN server that can be used by the ICEAgent to establish a connection with a peer")
if err := viper.BindPFlag("iceservers", cmd.PersistentFlags().Lookup("iceservers")); err != nil {
return err
}
return nil
}
func (s *WebRTC) Set() {
s.ICELite = viper.GetBool("icelite")
s.ICEServers = viper.GetStringSlice("iceserver")
s.NAT1To1IPs = viper.GetStringSlice("nat1to1")
s.ICELite = viper.GetBool("icelite")
s.ICEServers = []webrtc.ICEServer{}
iceServersJson := viper.GetString("iceservers")
if iceServersJson != "" {
err := json.Unmarshal([]byte(iceServersJson), &s.ICEServers)
if err != nil {
log.Panic().Err(err).Msg("failed to process iceservers")
}
}
iceServerSlice := viper.GetStringSlice("iceserver")
if len(iceServerSlice) > 0 {
s.ICEServers = append(s.ICEServers, webrtc.ICEServer{URLs: iceServerSlice})
}
if len(s.NAT1To1IPs) == 0 {
ip, err := utils.GetIP()
if err == nil {
s.NAT1To1IPs = append(s.NAT1To1IPs, ip)
ipfetch := viper.GetString("ipfetch")
ip, err := utils.GetIP(ipfetch)
if err != nil {
log.Panic().Err(err).Str("ipfetch", ipfetch).Msg("failed to fetch ip address")
}
s.NAT1To1IPs = append(s.NAT1To1IPs, ip)
}
min := uint16(59000)

View File

@ -2,6 +2,7 @@ package event
const (
SYSTEM_DISCONNECT = "system/disconnect"
SYSTEM_ERROR = "system/error"
)
const (

View File

@ -2,23 +2,26 @@ package message
import (
"n.eko.moe/neko/internal/types"
"github.com/pion/webrtc/v3"
)
type Message struct {
Event string `json:"event"`
}
type Disconnect struct {
type SystemMessage struct {
Event string `json:"event"`
Title string `json:"title"`
Message string `json:"message"`
}
type SignalProvide struct {
Event string `json:"event"`
ID string `json:"id"`
SDP string `json:"sdp"`
Lite bool `json:"lite"`
ICE []string `json:"ice"`
Event string `json:"event"`
ID string `json:"id"`
SDP string `json:"sdp"`
Lite bool `json:"lite"`
ICE []webrtc.ICEServer `json:"ice"`
}
type SignalAnswer struct {
@ -28,8 +31,8 @@ type SignalAnswer struct {
}
type SignalCandidate struct {
Event string `json:"event"`
Data string `json:"data"`
Event string `json:"event"`
Data string `json:"data"`
}
type MembersList struct {
@ -123,6 +126,6 @@ type BroadcastStatus struct {
}
type BroadcastCreate struct {
Event string `json:"event"`
URL string `json:"url"`
Event string `json:"event"`
URL string `json:"url"`
}

View File

@ -1,19 +1,16 @@
package types
import (
"time"
"github.com/pion/webrtc/v3"
"github.com/pion/webrtc/v3/pkg/media"
)
type Sample struct {
Data []byte
Timestamp time.Time
Duration time.Duration
}
type Sample media.Sample
type WebRTCManager interface {
Start()
Shutdown() error
CreatePeer(id string, session Session) (string, bool, []string, error)
CreatePeer(id string, session Session) (string, bool, []webrtc.ICEServer, error)
}
type Peer interface {

View File

@ -12,7 +12,7 @@ func ArrayIn(val interface{}, array interface{}) (exists bool, index int) {
case reflect.Slice:
s := reflect.ValueOf(array)
for i := 0; i < s.Len(); i++ {
if reflect.DeepEqual(val, s.Index(i).Interface()) == true {
if reflect.DeepEqual(val, s.Index(i).Interface()) {
index = i
exists = true
return

View File

@ -3,13 +3,29 @@ package utils
import (
"bytes"
"io/ioutil"
"net"
"net/http"
"time"
)
// dig @resolver1.opendns.com ANY myip.opendns.com +short -4
func GetIP() (string, error) {
rsp, err := http.Get("http://checkip.amazonaws.com")
func GetIP(serverUrl string) (string, error) {
tr := &http.Transport{
Proxy: nil, // ignore proxy
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 30,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 15 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := &http.Client{Transport: tr}
rsp, err := client.Get(serverUrl)
if err != nil {
return "", err
}

View File

@ -8,11 +8,13 @@ import (
"github.com/pion/webrtc/v3"
)
const OP_MOVE = 0x01
const OP_SCROLL = 0x02
const OP_KEY_DOWN = 0x03
const OP_KEY_UP = 0x04
const OP_KEY_CLK = 0x05
const (
OP_MOVE = 0x01
OP_SCROLL = 0x02
OP_KEY_DOWN = 0x03
OP_KEY_UP = 0x04
OP_KEY_CLK = 0x05
)
type PayloadHeader struct {
Event uint8
@ -63,7 +65,6 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
}
manager.remote.Move(int(payload.X), int(payload.Y))
break
case OP_SCROLL:
payload := &PayloadScroll{}
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
@ -77,7 +78,6 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
Msg("scroll")
manager.remote.Scroll(int(payload.X), int(payload.Y))
break
case OP_KEY_DOWN:
payload := &PayloadKey{}
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
@ -101,8 +101,6 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
manager.logger.Debug().Msgf("key down %d", payload.Key)
}
break
case OP_KEY_UP:
payload := &PayloadKey{}
err := binary.Read(buffer, binary.LittleEndian, payload)
@ -127,7 +125,6 @@ func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) e
manager.logger.Debug().Msgf("key up %d", payload.Key)
}
break
case OP_KEY_CLK:
// unused
break

View File

@ -63,7 +63,7 @@ func (manager *WebRTCManager) Start() {
manager.logger.Info().
Str("ice_lite", fmt.Sprintf("%t", manager.config.ICELite)).
Str("ice_servers", strings.Join(manager.config.ICEServers, ",")).
Str("ice_servers", fmt.Sprintf("%+v", manager.config.ICEServers)).
Str("ephemeral_port_range", fmt.Sprintf("%d-%d", manager.config.EphemeralMin, manager.config.EphemeralMax)).
Str("nat_ips", strings.Join(manager.config.NAT1To1IPs, ",")).
Msgf("webrtc starting")
@ -74,13 +74,9 @@ func (manager *WebRTCManager) Shutdown() error {
return nil
}
func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (string, bool, []string, error) {
func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (string, bool, []webrtc.ICEServer, error) {
configuration := &webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: manager.config.ICEServers,
},
},
ICEServers: manager.config.ICEServers,
SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback,
}
@ -99,7 +95,7 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (stri
settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
settings.SetICETimeouts(6 * time.Second, 6 * time.Second, 3 * time.Second)
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
settings.SetSRTPReplayProtectionWindow(512)
// Create MediaEngine based off sdp
@ -146,12 +142,12 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (stri
Msg("connection state has changed")
})
rtpVideo, err := connection.AddTrack(manager.videoTrack);
rtpVideo, err := connection.AddTrack(manager.videoTrack)
if err != nil {
return "", manager.config.ICELite, manager.config.ICEServers, err
}
rtpAudio, err := connection.AddTrack(manager.audioTrack);
rtpAudio, err := connection.AddTrack(manager.audioTrack)
if err != nil {
return "", manager.config.ICELite, manager.config.ICEServers, err
}
@ -234,7 +230,6 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (stri
}
}()
return description.SDP, manager.config.ICELite, manager.config.ICEServers, nil
}
@ -245,19 +240,19 @@ func (m *WebRTCManager) createTrack(codecName string) (*webrtc.TrackLocalStaticS
switch codecName {
case "VP8":
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "video/VP8", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 96}
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 96}
case "VP9":
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "video/VP9", ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 98}
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP9, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 98}
case "H264":
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "video/H264", ClockRate: 90000, Channels: 0, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", RTCPFeedback: fb}, PayloadType: 102}
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, Channels: 0, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", RTCPFeedback: fb}, PayloadType: 102}
case "Opus":
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "audio/opus", ClockRate: 48000, Channels: 2, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 111}
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 2, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 111}
case "G722":
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "audio/G722", ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 9}
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeG722, ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 9}
case "PCMU":
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "audio/PCMU", ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 0}
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypePCMU, ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 0}
case "PCMA":
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: "audio/PCMA", ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 8}
codec = webrtc.RTPCodecParameters{RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypePCMA, ClockRate: 8000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: fb}, PayloadType: 8}
default:
return nil, codec, fmt.Errorf("unknown codec %s", codecName)
}

View File

@ -12,7 +12,18 @@ func (h *MessageHandler) boradcastCreate(session types.Session, payload *message
return nil
}
h.broadcast.Create(payload.URL)
pipelineErr := h.broadcast.Create(payload.URL)
if pipelineErr != nil {
if err := session.Send(
message.SystemMessage{
Event: event.SYSTEM_ERROR,
Title: "Error while starting broadcast",
Message: pipelineErr.Error(),
}); err != nil {
h.logger.Warn().Err(err).Msgf("sending event %s has failed", event.SYSTEM_ERROR)
return err
}
}
if err := h.boradcastStatus(session); err != nil {
return err

View File

@ -54,7 +54,7 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
session, ok := h.sessions.Get(id)
if !ok {
errors.Errorf("unknown session id %s", id)
return errors.Errorf("unknown session id %s", id)
}
switch header.Event {
@ -90,7 +90,6 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
return h.controlKeyboard(id, session, payload)
}), "%s failed", header.Event)
// Chat Events
case event.CHAT_MESSAGE:
payload := &message.ChatReceive{}

View File

@ -3,6 +3,7 @@ package websocket
import (
"fmt"
"net/http"
"sync"
"sync/atomic"
"time"
@ -21,11 +22,12 @@ func New(sessions types.SessionManager, remote types.RemoteManager, broadcast ty
logger := log.With().Str("module", "websocket").Logger()
return &WebSocketHandler{
logger: logger,
conf: conf,
sessions: sessions,
remote: remote,
upgrader: websocket.Upgrader{
logger: logger,
shutdown: make(chan interface{}),
conf: conf,
sessions: sessions,
remote: remote,
upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
@ -47,14 +49,15 @@ func New(sessions types.SessionManager, remote types.RemoteManager, broadcast ty
const pingPeriod = 60 * time.Second
type WebSocketHandler struct {
logger zerolog.Logger
upgrader websocket.Upgrader
sessions types.SessionManager
remote types.RemoteManager
conf *config.WebSocket
handler *MessageHandler
conns uint32
shutdown chan bool
logger zerolog.Logger
wg sync.WaitGroup
shutdown chan interface{}
upgrader websocket.Upgrader
sessions types.SessionManager
remote types.RemoteManager
conf *config.WebSocket
handler *MessageHandler
conns uint32
}
func (ws *WebSocketHandler) Start() error {
@ -82,9 +85,11 @@ func (ws *WebSocketHandler) Start() error {
}
})
ws.wg.Add(1)
go func() {
defer func() {
ws.logger.Info().Msg("shutdown")
ws.wg.Done()
}()
current := ws.remote.ReadClipboard()
@ -116,7 +121,8 @@ func (ws *WebSocketHandler) Start() error {
}
func (ws *WebSocketHandler) Shutdown() error {
ws.shutdown <- true
close(ws.shutdown)
ws.wg.Wait()
return nil
}
@ -133,7 +139,7 @@ func (ws *WebSocketHandler) Upgrade(w http.ResponseWriter, r *http.Request) erro
if err != nil {
ws.logger.Warn().Err(err).Msg("authentication failed")
if err = connection.WriteJSON(message.Disconnect{
if err = connection.WriteJSON(message.SystemMessage{
Event: event.SYSTEM_DISCONNECT,
Message: "invalid_password",
}); err != nil {
@ -160,7 +166,7 @@ func (ws *WebSocketHandler) Upgrade(w http.ResponseWriter, r *http.Request) erro
}
if !ok {
if err = connection.WriteJSON(message.Disconnect{
if err = connection.WriteJSON(message.SystemMessage{
Event: event.SYSTEM_DISCONNECT,
Message: reason,
}); err != nil {
@ -250,11 +256,13 @@ func (ws *WebSocketHandler) handle(connection *websocket.Conn, id string) {
cancel := make(chan struct{})
ticker := time.NewTicker(pingPeriod)
ws.wg.Add(1)
go func() {
defer func() {
ticker.Stop()
ws.logger.Debug().Str("address", connection.RemoteAddr().String()).Msg("handle socket ending")
ws.handler.Disconnected(id)
ws.wg.Done()
}()
for {
@ -283,9 +291,21 @@ func (ws *WebSocketHandler) handle(connection *websocket.Conn, id string) {
if err := ws.handler.Message(id, raw); err != nil {
ws.logger.Error().Err(err).Msg("message handler has failed")
}
case <-ws.shutdown:
if err := connection.WriteJSON(message.SystemMessage{
Event: event.SYSTEM_DISCONNECT,
Message: "server_shutdown",
}); err != nil {
ws.logger.Err(err).Msg("failed to send disconnect")
}
if err := connection.Close(); err != nil {
ws.logger.Err(err).Msg("connection closed with an error")
}
return
case <-cancel:
return
case _ = <-ticker.C:
case <-ticker.C:
if err := connection.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}

Some files were not shown because too many files have changed in this diff Show More