kopia lustrzana https://codeberg.org/nmkj/audon
add gif indicator
rodzic
3469d62210
commit
a91d7d340a
|
@ -2,7 +2,7 @@ audon.localhost {
|
|||
tls /etc/caddy/certs/cert.pem /etc/caddy/certs/key.pem
|
||||
encode zstd gzip
|
||||
@backend {
|
||||
path /app/* /api/*
|
||||
path /app/* /api/* /storage/*
|
||||
}
|
||||
handle @backend {
|
||||
reverse_proxy devcontainer:8100
|
||||
|
|
|
@ -9,13 +9,19 @@
|
|||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {},
|
||||
"ghcr.io/devcontainers/features/go:1": {},
|
||||
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
|
||||
"packages": "libmagick++-dev,libwebp-dev"
|
||||
}
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": []
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "uname -a",
|
||||
// "onCreateCommand": "",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
|
|
|
@ -2,7 +2,7 @@ version: '3.1'
|
|||
|
||||
services:
|
||||
devcontainer:
|
||||
image: "mcr.microsoft.com/devcontainers/universal:2-linux"
|
||||
image: "mcr.microsoft.com/devcontainers/base:jammy"
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
command: sleep infinity
|
||||
|
@ -41,11 +41,13 @@ services:
|
|||
|
||||
livekit:
|
||||
image: livekit/livekit-server:v1.3
|
||||
command: --dev --bind 0.0.0.0
|
||||
command: --config /etc/livekit.yaml
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "7881:7881"
|
||||
- "7882:7882/udp"
|
||||
volumes:
|
||||
- ./livekit.yaml:/etc/livekit.yaml:ro
|
||||
|
||||
caddy:
|
||||
image: caddy:2
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
# main TCP port for RoomService and RTC endpoint
|
||||
# for production setups, this port should be placed behind a load balancer with TLS
|
||||
port: 7880
|
||||
|
||||
rtc:
|
||||
tcp_port: 7881
|
||||
port_range_start: 7882
|
||||
port_range_end: 7882
|
||||
|
||||
turn:
|
||||
enabled: false
|
||||
|
||||
keys:
|
||||
devkey: secret
|
||||
|
||||
webhook:
|
||||
api_key: devkey
|
||||
urls:
|
||||
- http://devcontainer:8100/app/webhook
|
||||
|
||||
room:
|
||||
auto_create: false
|
||||
empty_timeout: 30
|
||||
max_participants: 0
|
||||
max_metadata_size: 0
|
||||
|
||||
audio:
|
||||
active_level: 50
|
||||
min_percentile: 20
|
||||
update_interval: 200
|
|
@ -0,0 +1 @@
|
|||
public/storage/
|
|
@ -189,3 +189,4 @@ config/*
|
|||
mongo/
|
||||
redis/
|
||||
cache/
|
||||
public/storage
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"name": "Launch Go",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -17,19 +17,25 @@ RUN go mod download -x
|
|||
|
||||
COPY *.go /workspace/
|
||||
|
||||
RUN go build -v -o audon-bin .
|
||||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install libmagick++-dev libwebp-dev && \
|
||||
go build -v -o audon-bin .
|
||||
|
||||
FROM debian:bullseye
|
||||
RUN
|
||||
|
||||
FROM ubuntu:jammy
|
||||
|
||||
WORKDIR /audon
|
||||
|
||||
COPY --from=0 /workspace/dist /audon/audon-fe/dist
|
||||
COPY --from=1 /workspace/audon-bin /audon/
|
||||
COPY locales /audon/locales
|
||||
COPY public /audon/public
|
||||
|
||||
RUN echo "Etc/UTC" > /etc/localtime && \
|
||||
apt-get update && apt-get upgrade -y && \
|
||||
apt-get -y --no-install-recommends install \
|
||||
imagemagick libwebp \
|
||||
tini \
|
||||
tzdata \
|
||||
ca-certificates
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "audon-fe",
|
||||
"version": "0.1.0-alpha2",
|
||||
"version": "0.1.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "audon-fe",
|
||||
"version": "0.1.0-alpha2",
|
||||
"version": "0.1.2",
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||
"@picmo/popup-picker": "^5.7.2",
|
||||
|
@ -16,10 +16,10 @@
|
|||
"@vueuse/core": "^9.6.0",
|
||||
"axios": "^1.2.0",
|
||||
"howler": "^2.2.3",
|
||||
"livekit-client": "^1.5.0",
|
||||
"livekit-client": "^1.6.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"luxon": "^3.1.1",
|
||||
"masto": "^4.9.0",
|
||||
"masto": "^5.6.0",
|
||||
"picmo": "^5.7.2",
|
||||
"pinia": "^2.0.26",
|
||||
"vue": "^3.2.45",
|
||||
|
@ -40,9 +40,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
|
||||
"integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==",
|
||||
"version": "7.20.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
|
||||
"integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
|
@ -226,11 +226,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "9.3.0-beta.14",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.3.0-beta.14.tgz",
|
||||
"integrity": "sha512-PlZ3pl+YYuql54Nq+26wv6ohIa8Ig6ALrvQI+f2zZKUtkupb49M4wyVN3bDQbFlgYVE7/u3n19BJSY8lEuX5Eg==",
|
||||
"version": "9.3.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.3.0-beta.16.tgz",
|
||||
"integrity": "sha512-CGQI3xRcs1ET75eDQ0DUy3MRYOqTauRIIgaMoISKiF83gqRWg93FqN8lGMKcpBqaF4tI0JhsfosCaGiBL9+dnw==",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.3.0-beta.14",
|
||||
"@intlify/shared": "9.3.0-beta.16",
|
||||
"source-map": "0.6.1"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -241,9 +241,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "9.3.0-beta.14",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.3.0-beta.14.tgz",
|
||||
"integrity": "sha512-mJ/rFan+4uVsBAQSCAJnpQaPvSjQ49mJMNmGelTUbTDAmgf0oexYxwqtKOlFFyY3hmQ8lUDYaGQKuYrFgRuHnA==",
|
||||
"version": "9.3.0-beta.16",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.3.0-beta.16.tgz",
|
||||
"integrity": "sha512-kXbm4svALe3lX+EjdJxfnabOphqS4yQ1Ge/iIlR8tvUiYRCoNz3hig1M4336iY++Dfx5ytEQJPNjIcknNIuvig==",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
|
@ -309,6 +309,18 @@
|
|||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mastojs/ponyfills": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@mastojs/ponyfills/-/ponyfills-1.0.4.tgz",
|
||||
"integrity": "sha512-1NaIGmcU7OmyNzx0fk+cYeGTkdXlOJOSdetaC4pStVWsrhht2cdlYSAfe5NDW3FcUmcEm2vVceB9lcClN1RCxw==",
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.17",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"abort-controller": "^3.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
"node-fetch": "^2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@mdi/js": {
|
||||
"version": "7.1.96",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.1.96.tgz",
|
||||
|
@ -438,6 +450,28 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
|
||||
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz",
|
||||
"integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-fetch/node_modules/form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
||||
|
@ -508,9 +542,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
"version": "6.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz",
|
||||
"integrity": "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz",
|
||||
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
|
||||
},
|
||||
"node_modules/@vue/eslint-config-prettier": {
|
||||
"version": "7.0.0",
|
||||
|
@ -681,13 +715,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "9.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.10.0.tgz",
|
||||
"integrity": "sha512-CxMewME07qeuzuT/AOIQGv0EhhDoojniqU6pC3F8m5VC76L47UT18DcX88kWlP3I7d3qMJ4u/PD8iSRsy3bmNA==",
|
||||
"version": "9.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.11.1.tgz",
|
||||
"integrity": "sha512-E/cizD1w9ILkq4axYjZrXLkKaBfzloaby2n3NMjUfd6yI/jkfTVgc6iwy/Cw2e++Ld4LphGbO+3MhzizvwUslQ==",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.16",
|
||||
"@vueuse/metadata": "9.10.0",
|
||||
"@vueuse/shared": "9.10.0",
|
||||
"@vueuse/metadata": "9.11.1",
|
||||
"@vueuse/shared": "9.11.1",
|
||||
"vue-demi": "*"
|
||||
},
|
||||
"funding": {
|
||||
|
@ -720,17 +754,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "9.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.10.0.tgz",
|
||||
"integrity": "sha512-G5VZhgTCapzU9rv0Iq2HBrVOSGzOKb+OE668NxhXNcTjUjwYxULkEhAw70FtRLMZc+hxcFAzDZlKYA0xcwNMuw==",
|
||||
"version": "9.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.11.1.tgz",
|
||||
"integrity": "sha512-ABjkrG+VXggNhjfGyw5e/sekxTZfXTwjrYXkkWQmQ7Biyv+Gq9UD6IDNfeGvQZEINI0Qzw6nfuO2UFCd3hlrxQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "9.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.10.0.tgz",
|
||||
"integrity": "sha512-vakHJ2ZRklAzqmcVBL38RS7BxdBA4+5poG9NsSyqJxrt9kz0zX3P5CXMy0Hm6LFbZXUgvKdqAS3pUH1zX/5qTQ==",
|
||||
"version": "9.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.11.1.tgz",
|
||||
"integrity": "sha512-UTZYGAjT96hWn4buf4wstZbeheBVNcKPQuej6qpoSkjF1atdaeCD6kqm9uGL2waHfisSgH9mq0qCRiBOk5C/2w==",
|
||||
"dependencies": {
|
||||
"vue-demi": "*"
|
||||
},
|
||||
|
@ -763,6 +797,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.8.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
|
||||
|
@ -850,9 +895,9 @@
|
|||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz",
|
||||
"integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.3.tgz",
|
||||
"integrity": "sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
|
@ -900,6 +945,18 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
|
@ -1492,9 +1549,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.31.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz",
|
||||
"integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz",
|
||||
"integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^1.4.1",
|
||||
|
@ -1581,9 +1638,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-vue": {
|
||||
"version": "9.8.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.8.0.tgz",
|
||||
"integrity": "sha512-E/AXwcTzunyzM83C2QqDHxepMzvI2y6x+mmeYHbVDQlKFqmKYvRrhaVixEeeG27uI44p9oKDFiyCRw4XxgtfHA==",
|
||||
"version": "9.9.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.9.0.tgz",
|
||||
"integrity": "sha512-YbubS7eK0J7DCf0U2LxvVP7LMfs6rC6UltihIgval3azO3gyDwEGVgsCMe1TmDiEkl6GdMKfRpaME6QxIYtzDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint-utils": "^3.0.0",
|
||||
|
@ -1714,6 +1771,14 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.0.tgz",
|
||||
|
@ -1914,8 +1979,20 @@
|
|||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"devOptional": true
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
|
||||
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
|
@ -1974,7 +2051,6 @@
|
|||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
|
@ -1991,6 +2067,17 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/header-case": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz",
|
||||
|
@ -2120,27 +2207,6 @@
|
|||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/isomorphic-form-data": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-form-data/-/isomorphic-form-data-2.0.0.tgz",
|
||||
"integrity": "sha512-TYgVnXWeESVmQSg4GLVbalmQ+B4NPi/H4eWxqALKj63KsUrcu301YDjBqaOw3h+cbak7Na4Xyps3BiptHtxTfg==",
|
||||
"dependencies": {
|
||||
"form-data": "^2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-form-data/node_modules/form-data": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
|
||||
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-ws": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz",
|
||||
|
@ -2150,9 +2216,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/js-sdsl": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
|
||||
"integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
|
||||
"integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
|
@ -2399,27 +2465,17 @@
|
|||
}
|
||||
},
|
||||
"node_modules/masto": {
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/masto/-/masto-4.11.1.tgz",
|
||||
"integrity": "sha512-siTQNhfLV1JjOERCGgjagMvD6q0K0hLuhOXrbXNcYzHAwpbPeSeAM6CSpIRrZ8zFDepOR62Djs/GtJdTR21Rfw==",
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/masto/-/masto-5.6.0.tgz",
|
||||
"integrity": "sha512-+5t6bSt/V0HyL3hDowxabVszfLjkrKVunhgWXC4LrCBMjMKIOF4vrFJriXSPKOprB6VCZztvGGuy02zSy7oRcQ==",
|
||||
"dependencies": {
|
||||
"axios": "1.1.3",
|
||||
"@mastojs/ponyfills": "^1.0.4",
|
||||
"change-case": "^4.1.2",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"isomorphic-form-data": "^2.0.0",
|
||||
"isomorphic-ws": "^5.0.0",
|
||||
"qs": "^6.11.0",
|
||||
"semver": "^7.3.7",
|
||||
"ws": "^8.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/masto/node_modules/axios": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.1.3.tgz",
|
||||
"integrity": "sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
"ws": "^8.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
|
@ -2504,6 +2560,25 @@
|
|||
"tslib": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.8",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz",
|
||||
"integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
|
@ -2524,6 +2599,14 @@
|
|||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
@ -2662,9 +2745,9 @@
|
|||
"devOptional": true
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.0.0.tgz",
|
||||
"integrity": "sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg=="
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz",
|
||||
"integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w=="
|
||||
},
|
||||
"node_modules/picmo": {
|
||||
"version": "5.7.2",
|
||||
|
@ -2694,9 +2777,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/pinia": {
|
||||
"version": "2.0.28",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.28.tgz",
|
||||
"integrity": "sha512-YClq9DkqCblq9rlyUual7ezMu/iICWdBtfJrDt4oWU9Zxpijyz7xB2xTwx57DaBQ96UGvvTMORzALr+iO5PVMw==",
|
||||
"version": "2.0.29",
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.29.tgz",
|
||||
"integrity": "sha512-5z/KpFecq/cIgfeTnulJXldiLcTITRkTe3N58RKYSj0Pc1EdR6oyCdnf5A9jLoVwBqX5LtHhd0kGlpzWvk9oiQ==",
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.4.5",
|
||||
"vue-demi": "*"
|
||||
|
@ -2853,9 +2936,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.2.tgz",
|
||||
"integrity": "sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw==",
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
|
||||
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
|
@ -2908,14 +2991,28 @@
|
|||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.2.0.tgz",
|
||||
"integrity": "sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==",
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
@ -3112,6 +3209,19 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/snake-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz",
|
||||
|
@ -3208,6 +3318,11 @@
|
|||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/ts-debounce": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz",
|
||||
|
@ -3463,9 +3578,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vuetify": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.1.1.tgz",
|
||||
"integrity": "sha512-BkfRQZ406xQORpgrcUjuPaT/woO96ef/+2zHCfL3an+CrUhjG/sIAptEybHruq3xwFM0uJibDFqGiridsXc99w==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.1.2.tgz",
|
||||
"integrity": "sha512-vzxfVc1KlEmZ6tHjmrKCt25rKL6YkakbvQAZ0f/s03kHd0pjXKX4pcWuYgA5mZz4yrfZh/cRHvgGt+7A9CXOVg==",
|
||||
"engines": {
|
||||
"node": "^12.20 || >=14.13"
|
||||
},
|
||||
|
@ -3491,6 +3606,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/webpack-sources": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
|
@ -3516,6 +3636,15 @@
|
|||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
"@vueuse/core": "^9.6.0",
|
||||
"axios": "^1.2.0",
|
||||
"howler": "^2.2.3",
|
||||
"livekit-client": "^1.5.0",
|
||||
"livekit-client": "^1.6.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"luxon": "^3.1.1",
|
||||
"masto": "^4.9.0",
|
||||
"masto": "^5.6.0",
|
||||
"picmo": "^5.7.2",
|
||||
"pinia": "^2.0.26",
|
||||
"vue": "^3.2.45",
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script>
|
||||
import { RouterView, RouterLink } from "vue-router";
|
||||
import { RouterView } from "vue-router";
|
||||
import locales from "./locales";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RouterView,
|
||||
RouterLink,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
|
@ -33,13 +32,11 @@ export default {
|
|||
</div>
|
||||
<v-system-bar window>
|
||||
<div class="d-flex justify-center align-center w-100">
|
||||
<RouterLink :to="{ name: 'home' }" class="d-flex align-center">
|
||||
<img
|
||||
height="20"
|
||||
src="./assets/img/audon-logo-orange.svg"
|
||||
alt="Branding Logo"
|
||||
/>
|
||||
</RouterLink>
|
||||
<img
|
||||
height="20"
|
||||
src="./assets/img/audon-logo-orange.svg"
|
||||
alt="Branding Logo"
|
||||
/>
|
||||
</div>
|
||||
</v-system-bar>
|
||||
<v-main>
|
||||
|
|
|
@ -3,7 +3,7 @@ import router from "../router";
|
|||
|
||||
export const validators = {
|
||||
fqdn: helpers.regex(
|
||||
/^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$/,
|
||||
/^[a-zA-Z]([a-zA-Z0-9-]+[.]?)*[a-zA-Z0-9]$/,
|
||||
/^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$/
|
||||
),
|
||||
};
|
||||
|
@ -11,7 +11,8 @@ export const validators = {
|
|||
export function webfinger(user) {
|
||||
if (!user) return "";
|
||||
const url = new URL(user.url);
|
||||
return `${user.acct}@${url.host}`;
|
||||
const finger = user.acct.split("@");
|
||||
return `${finger[0]}@${url.host}`;
|
||||
}
|
||||
|
||||
export function pushNotFound(route) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { defineStore } from "pinia";
|
||||
import axios from "axios";
|
||||
import { login } from "masto";
|
||||
import router from "../router";
|
||||
import { webfinger } from "../assets/utils";
|
||||
|
||||
export const useMastodonStore = defineStore("mastodon", {
|
||||
|
@ -11,7 +10,7 @@ export const useMastodonStore = defineStore("mastodon", {
|
|||
oauth: {
|
||||
url: "",
|
||||
token: "",
|
||||
audon_id: "",
|
||||
audon: null,
|
||||
},
|
||||
client: null,
|
||||
userinfo: null,
|
||||
|
@ -35,19 +34,32 @@ export const useMastodonStore = defineStore("mastodon", {
|
|||
disableVersionCheck: true,
|
||||
});
|
||||
this.client = client;
|
||||
this.userinfo = await client.accounts.verifyCredentials();
|
||||
const user = await client.v1.accounts.verifyCredentials();
|
||||
this.userinfo = user;
|
||||
this.authorized = true;
|
||||
},
|
||||
async callMastodonAPI(caller, ...args) {
|
||||
try {
|
||||
return await caller(...args);
|
||||
} catch (error) {
|
||||
if (error.response?.status === 401) {
|
||||
this.$reset();
|
||||
router.push({ name: "login" });
|
||||
async updateAvatar(img) {
|
||||
if (this.client === null) return;
|
||||
const avatar = await (await fetch(img)).blob();
|
||||
this.userinfo = await this.client.v1.accounts.updateCredentials({
|
||||
avatar,
|
||||
});
|
||||
},
|
||||
async revertAvatar() {
|
||||
const t = setTimeout(async () => {
|
||||
const oldAvatar = sessionStorage.getItem("avatar_old");
|
||||
sessionStorage.removeItem("avatar_old");
|
||||
sessionStorage.removeItem("avatar_timeout");
|
||||
if (this.client === null || !oldAvatar) return;
|
||||
const resp = await axios.delete("/api/room");
|
||||
if (resp.status === 200) {
|
||||
const avatar = await (await fetch(oldAvatar)).blob();
|
||||
this.userinfo = await this.client.v1.accounts.updateCredentials({
|
||||
avatar,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}, 2 * 1000);
|
||||
sessionStorage.setItem("avatar_timeout", t.toString());
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ import { useClipboard } from "@vueuse/core";
|
|||
import { useMastodonStore } from "../stores/mastodon";
|
||||
import { helpers, maxLength, required } from "@vuelidate/validators";
|
||||
import { debounce, some, map, truncate, trim } from "lodash-es";
|
||||
import { login } from "masto";
|
||||
import { webfinger } from "../assets/utils";
|
||||
import axios from "axios";
|
||||
|
||||
|
@ -124,31 +123,26 @@ export default {
|
|||
},
|
||||
relationship(to) {
|
||||
this.advertise = to === "everyone";
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async search(val) {
|
||||
const finger = val.split("@");
|
||||
if (finger.length < 2) return;
|
||||
else if (finger.length === 3) {
|
||||
finger.splice(0, 1);
|
||||
this.searchQuery = finger.join("@");
|
||||
}
|
||||
if (finger.length !== 2) return;
|
||||
try {
|
||||
const url = new URL(`https://${finger[1]}`);
|
||||
const client = await login({
|
||||
url: url.toString(),
|
||||
disableVersionCheck: true,
|
||||
const resp = await this.donStore.client.v1.accounts.search({
|
||||
q: val,
|
||||
resolve: true,
|
||||
});
|
||||
const user = await client.accounts.lookup({ acct: finger[0] });
|
||||
if (resp.length != 1) throw "";
|
||||
const user = resp[0];
|
||||
user.finger = webfinger(user);
|
||||
this.searchResult = user;
|
||||
this.searchError.enabled = false;
|
||||
} catch (error) {
|
||||
if (error.isMastoError && error.statusCode === 404) {
|
||||
this.searchError.message = this.$t("errors.notFound", { value: val });
|
||||
this.searchError.colour = "error";
|
||||
this.searchError.enabled = true;
|
||||
}
|
||||
this.searchError.message = this.$t("errors.notFound", { value: val });
|
||||
this.searchError.colour = "error";
|
||||
this.searchError.enabled = true;
|
||||
} finally {
|
||||
this.isCandiadateLoading = false;
|
||||
}
|
||||
|
@ -380,7 +374,9 @@ export default {
|
|||
<template v-slot:label>
|
||||
<i18n-t keypath="form.advertise" tag="div">
|
||||
<template v-slot:bot>
|
||||
<a href="https://akkoma.audon.space/users/now" target="_blank"
|
||||
<a
|
||||
href="https://akkoma.audon.space/users/now"
|
||||
target="_blank"
|
||||
>now@audon.space</a
|
||||
>
|
||||
</template>
|
||||
|
|
|
@ -39,15 +39,11 @@ import requestSound from "../assets/request.oga";
|
|||
|
||||
const publishOpts = {
|
||||
audioBitrate: AudioPresets.music,
|
||||
// forceStereo: true,
|
||||
};
|
||||
|
||||
const captureOpts = {
|
||||
// autoGainControl: false,
|
||||
// echoCancellation: false,
|
||||
// sampleRate: 48000,
|
||||
// sampleSize: 16,
|
||||
// channelCount: 2
|
||||
autoGainControl: true,
|
||||
echoCancellation: true,
|
||||
};
|
||||
|
||||
export default {
|
||||
|
@ -64,6 +60,7 @@ export default {
|
|||
return {
|
||||
webfinger,
|
||||
clone,
|
||||
noSleep,
|
||||
mdiLogout,
|
||||
mdiAccountVoice,
|
||||
mdiMicrophone,
|
||||
|
@ -180,6 +177,7 @@ export default {
|
|||
}
|
||||
} catch (error) {
|
||||
let query = { l: `/r/${this.roomID}` };
|
||||
this.noSleep.disable();
|
||||
switch (error.response?.status) {
|
||||
case 404:
|
||||
pushNotFound(this.$route);
|
||||
|
@ -212,7 +210,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
iamMuted() {
|
||||
const myAudonID = this.donStore.oauth.audon_id;
|
||||
const myAudonID = this.donStore.oauth.audon?.audon_id;
|
||||
return (
|
||||
(this.iamHost || this.iamCohost || this.iamSpeaker) &&
|
||||
this.micGranted &&
|
||||
|
@ -220,7 +218,7 @@ export default {
|
|||
);
|
||||
},
|
||||
iamHost() {
|
||||
const myAudonID = this.donStore.oauth.audon_id;
|
||||
const myAudonID = this.donStore.oauth.audon?.audon_id;
|
||||
if (!myAudonID) return false;
|
||||
|
||||
return this.isHost(myAudonID);
|
||||
|
@ -232,7 +230,7 @@ export default {
|
|||
return this.isCohost({ remote_id: myInfo.id, remote_url: myInfo.url });
|
||||
},
|
||||
iamSpeaker() {
|
||||
const myAudonID = this.donStore.oauth.audon_id;
|
||||
const myAudonID = this.donStore.oauth.audon?.audon_id;
|
||||
if (!myAudonID) return false;
|
||||
|
||||
return this.isSpeaker(myAudonID);
|
||||
|
@ -263,33 +261,44 @@ export default {
|
|||
async joinRoom() {
|
||||
if (!this.donStore.authorized) return;
|
||||
try {
|
||||
const resp = await axios.get(`/api/room/${this.roomID}`);
|
||||
const room = new Room({
|
||||
// adaptiveStream: false,
|
||||
// dynacast: false,
|
||||
// publishDefaults: {
|
||||
// stopMicTrackOnMute: false,
|
||||
// simulcast: false,
|
||||
// },
|
||||
const timeout = sessionStorage.getItem("avatar_timeout");
|
||||
if (timeout) {
|
||||
const timeoutID = parseInt(timeout);
|
||||
clearTimeout(timeoutID);
|
||||
sessionStorage.removeItem("avatar_timeout");
|
||||
}
|
||||
await this.donStore.fetchToken();
|
||||
let avatarURL = this.donStore.userinfo.avatar;
|
||||
if (this.donStore.oauth.audon?.avatar) {
|
||||
avatarURL = "";
|
||||
}
|
||||
const resp = await axios.postForm(`/api/room/${this.roomID}`, {
|
||||
avatar: avatarURL,
|
||||
});
|
||||
sessionStorage.setItem("avatar_old", resp.data.original);
|
||||
if (resp.data.indicator && !timeout) {
|
||||
try {
|
||||
await this.donStore.updateAvatar(resp.data.indicator);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
const room = new Room();
|
||||
const self = this;
|
||||
room
|
||||
.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
|
||||
.on(RoomEvent.TrackSubscribed, (track) => {
|
||||
if (track.kind === Track.Kind.Audio) {
|
||||
const element = track.attach();
|
||||
self.$refs.audioDOM.appendChild(element);
|
||||
}
|
||||
})
|
||||
.on(
|
||||
RoomEvent.TrackUnsubscribed,
|
||||
(track, publication, participant) => {
|
||||
track.detach();
|
||||
}
|
||||
)
|
||||
.on(RoomEvent.LocalTrackPublished, (publication, participant) => {
|
||||
.on(RoomEvent.TrackUnsubscribed, (track) => {
|
||||
track.detach();
|
||||
})
|
||||
.on(RoomEvent.LocalTrackPublished, () => {
|
||||
self.micGranted = true;
|
||||
})
|
||||
.on(RoomEvent.LocalTrackUnpublished, (publication, participant) => {
|
||||
.on(RoomEvent.LocalTrackUnpublished, (publication) => {
|
||||
publication.track?.detach();
|
||||
})
|
||||
.on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
||||
|
@ -302,8 +311,6 @@ export default {
|
|||
self.fetchMastoData(participant.identity, metadata);
|
||||
}
|
||||
})
|
||||
.on(RoomEvent.TrackMuted, (publication, participant) => {})
|
||||
.on(RoomEvent.TrackUnmuted, (publication, participant) => {})
|
||||
.on(RoomEvent.ParticipantDisconnected, (participant) => {
|
||||
self.participants = omit(self.participants, participant.identity);
|
||||
})
|
||||
|
@ -314,25 +321,30 @@ export default {
|
|||
})
|
||||
.on(RoomEvent.Disconnected, (reason) => {
|
||||
// TODO: change this from alert to a vuetify thing
|
||||
let message = "";
|
||||
switch (reason) {
|
||||
case DisconnectReason.ROOM_DELETED:
|
||||
message = self.$t("roomEvent.closedByHost");
|
||||
break;
|
||||
case DisconnectReason.PARTICIPANT_REMOVED:
|
||||
message = self.$t("roomEvent.removed");
|
||||
break;
|
||||
case DisconnectReason.CLIENT_INITIATED:
|
||||
break;
|
||||
default:
|
||||
message = "Disconnected due to unknown reasons";
|
||||
self.noSleep.disable();
|
||||
if (reason === DisconnectReason.PARTICIPANT_REMOVED) {
|
||||
alert(self.$t("roomEvent.removed"));
|
||||
self.$router.push({ name: "home" });
|
||||
} else {
|
||||
self.donStore.revertAvatar().finally(() => {
|
||||
let message = "";
|
||||
switch (reason) {
|
||||
case DisconnectReason.ROOM_DELETED:
|
||||
message = self.$t("roomEvent.closedByHost");
|
||||
break;
|
||||
case DisconnectReason.CLIENT_INITIATED:
|
||||
break;
|
||||
default:
|
||||
message = "Disconnected due to unknown reasons";
|
||||
}
|
||||
if (message !== "") {
|
||||
alert(message);
|
||||
}
|
||||
self.$router.push({ name: "home" });
|
||||
});
|
||||
}
|
||||
if (message !== "") {
|
||||
alert(message);
|
||||
}
|
||||
self.$router.push({ name: "home" });
|
||||
})
|
||||
.on(RoomEvent.DataReceived, (payload, participant, kind) => {
|
||||
.on(RoomEvent.DataReceived, (payload, participant) => {
|
||||
try {
|
||||
/* data should be like
|
||||
{ "kind": "speak_request" }
|
||||
|
@ -380,7 +392,7 @@ export default {
|
|||
if (self.iamSpeaker && !self.micGranted) {
|
||||
self.roomClient.localParticipant
|
||||
.setMicrophoneEnabled(true, captureOpts, publishOpts)
|
||||
.then((v) => {
|
||||
.then(() => {
|
||||
self.micGranted = true;
|
||||
})
|
||||
.finally(() => {
|
||||
|
@ -396,7 +408,7 @@ export default {
|
|||
for (const part of room.participants.values()) {
|
||||
this.addParticipant(part);
|
||||
}
|
||||
this.mutedSpeakerIDs.add(this.donStore.oauth.audon_id);
|
||||
this.mutedSpeakerIDs.add(this.donStore.oauth.audon.audon_id);
|
||||
this.activeSpeakerIDs = new Set(
|
||||
map(room.activeSpeakers, (p) => p.identity)
|
||||
);
|
||||
|
@ -420,9 +432,9 @@ export default {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
let message = "";
|
||||
switch (error.response?.status) {
|
||||
case 403:
|
||||
let message = "";
|
||||
switch (error.response?.data) {
|
||||
case "following":
|
||||
message = this.$t("errors.restriction.following");
|
||||
|
@ -456,6 +468,7 @@ export default {
|
|||
default:
|
||||
alert(error);
|
||||
}
|
||||
this.noSleep.disable();
|
||||
this.$router.push({ name: "home" });
|
||||
}
|
||||
},
|
||||
|
@ -472,7 +485,7 @@ export default {
|
|||
onResize() {
|
||||
const mainArea = document.getElementById("mainArea");
|
||||
const height = mainArea.clientHeight;
|
||||
this.mainHeight = height > 700 ? 700 : window.innerHeight - 95;
|
||||
this.mainHeight = height > 700 ? 700 : window.innerHeight - 110;
|
||||
},
|
||||
isHost(identity) {
|
||||
return identity === this.roomInfo.host?.audon_id;
|
||||
|
@ -606,7 +619,9 @@ export default {
|
|||
url: url.origin,
|
||||
disableVersionCheck: true,
|
||||
});
|
||||
const info = await mastoClient.accounts.fetch(remote_id);
|
||||
const info = await mastoClient.v1.accounts.fetch(remote_id);
|
||||
const resp = await axios.get(`/api/user/${identity}`);
|
||||
info.avatar = `/storage/${resp.data.audon_id}/avatar/${resp.data.avatar}`;
|
||||
this.cachedMastoData[identity] = info;
|
||||
} catch (error) {
|
||||
// FIXME: display error snackbar
|
||||
|
@ -651,13 +666,19 @@ export default {
|
|||
async onRoomClose() {
|
||||
// TODO: change this from confirm to a vuetify thing
|
||||
if (confirm(this.$t("closeRoomConfirm"))) {
|
||||
this.loading = true;
|
||||
try {
|
||||
await axios.delete(`/api/room/${this.roomID}`);
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
async onLeave() {
|
||||
await this.roomClient.disconnect();
|
||||
},
|
||||
async onStartListening() {
|
||||
try {
|
||||
await this.roomClient.startAudio();
|
||||
|
@ -945,6 +966,7 @@ export default {
|
|||
v-if="iamHost"
|
||||
:icon="mdiLogout"
|
||||
color="red"
|
||||
:disabled="loading"
|
||||
@click="onRoomClose"
|
||||
variant="flat"
|
||||
></v-btn>
|
||||
|
@ -952,7 +974,8 @@ export default {
|
|||
v-else
|
||||
:icon="mdiLogout"
|
||||
color="red"
|
||||
@click="roomClient.disconnect()"
|
||||
:disabled="loading"
|
||||
@click="onLeave"
|
||||
variant="flat"
|
||||
></v-btn>
|
||||
<v-badge
|
||||
|
|
|
@ -20,10 +20,7 @@ func verifyTokenInSession(c echo.Context) (bool, *mastodon.Account, error) {
|
|||
return false, nil, err
|
||||
}
|
||||
|
||||
mastoClient, err := getMastodonClient(c)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
mastoClient := getMastodonClient(data)
|
||||
if mastoClient == nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
@ -72,7 +69,7 @@ func loginHandler(c echo.Context) (err error) {
|
|||
// mastApp, err := mastodon.RegisterApp(c.Request().Context(), appConfig)
|
||||
mastApp, err := registerApp(c.Request().Context(), appConfig)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
c.Logger().Warn(err)
|
||||
return echo.NewHTTPError(http.StatusNotFound, "server_not_found")
|
||||
}
|
||||
|
||||
|
@ -150,11 +147,12 @@ func oauthHandler(c echo.Context) (err error) {
|
|||
}
|
||||
data.AudonID = id.String()
|
||||
acctUrl, _ := url.Parse(acc.URL)
|
||||
finger := strings.Split(acc.Username, "@")
|
||||
newUser := AudonUser{
|
||||
AudonID: data.AudonID,
|
||||
RemoteID: string(acc.ID),
|
||||
RemoteURL: acc.URL,
|
||||
Webfinger: fmt.Sprintf("%s@%s", acc.Username, acctUrl.Host),
|
||||
Webfinger: fmt.Sprintf("%s@%s", finger[0], acctUrl.Host),
|
||||
CreatedAt: time.Now().UTC(),
|
||||
}
|
||||
if _, insertErr := coll.InsertOne(c.Request().Context(), newUser); insertErr != nil {
|
||||
|
@ -184,11 +182,15 @@ func getOAuthTokenHandler(c echo.Context) (err error) {
|
|||
if !ok {
|
||||
return ErrInvalidSession
|
||||
}
|
||||
user, ok := c.Get("user").(*AudonUser)
|
||||
if !ok {
|
||||
return ErrInvalidSession
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, &TokenResponse{
|
||||
Url: data.MastodonConfig.Server,
|
||||
Token: data.MastodonConfig.AccessToken,
|
||||
AudonID: data.AudonID,
|
||||
Url: data.MastodonConfig.Server,
|
||||
Token: data.MastodonConfig.AccessToken,
|
||||
Audon: user,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/sizeofint/webpanimation"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"golang.org/x/image/draw"
|
||||
"golang.org/x/image/webp"
|
||||
"gopkg.in/gographics/imagick.v2/imagick"
|
||||
)
|
||||
|
||||
func (u *AudonUser) GetIndicator(ctx context.Context, fnew []byte) ([]byte, error) {
|
||||
if u == nil {
|
||||
return nil, errors.New("nil user")
|
||||
}
|
||||
|
||||
mtype := mimetype.Detect(fnew)
|
||||
if !mimetype.EqualsAny(mtype.String(), "image/png", "image/jpeg", "image/webp", "image/gif") {
|
||||
return nil, errors.New("file type not supported")
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(fnew)
|
||||
isAvatarNew := false
|
||||
|
||||
var err error
|
||||
|
||||
// Check if user's original avatar exists
|
||||
saved := u.GetOriginalAvatarPath(hash, mtype)
|
||||
if _, err := os.Stat(saved); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(saved), 0775); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write user's avatar if the original version doesn't exist
|
||||
if err := os.WriteFile(saved, fnew, 0664); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.AvatarFile != "" {
|
||||
os.Remove(u.getAvatarImagePath(u.AvatarFile))
|
||||
}
|
||||
isAvatarNew = true
|
||||
}
|
||||
|
||||
fname := filepath.Base(saved)
|
||||
coll := mainDB.Collection(COLLECTION_USER)
|
||||
if _, err = coll.UpdateOne(ctx,
|
||||
bson.D{{Key: "audon_id", Value: u.AudonID}},
|
||||
bson.D{
|
||||
{Key: "$set", Value: bson.D{{Key: "avatar", Value: fname}}},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isAvatarNew {
|
||||
if data, err := os.ReadFile(u.GetOriginalAvatarPath(hash, mtype)); err == nil {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(fnew)
|
||||
|
||||
var newImg image.Image
|
||||
if mtype.Is("image/png") {
|
||||
newImg, err = png.Decode(buf)
|
||||
} else if mtype.Is("image/jpeg") {
|
||||
newImg, err = jpeg.Decode(buf)
|
||||
} else if mtype.Is("image/webp") {
|
||||
newImg, err = webp.Decode(buf)
|
||||
} else if mtype.Is("image/gif") {
|
||||
newImg, err = gif.Decode(buf)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return u.createGIF(newImg)
|
||||
}
|
||||
|
||||
func (u *AudonUser) createGIF(avatar image.Image) ([]byte, error) {
|
||||
avatarPNG := image.NewRGBA(image.Rect(0, 0, 150, 150))
|
||||
draw.BiLinear.Scale(avatarPNG, avatarPNG.Rect, avatar, avatar.Bounds(), draw.Src, nil)
|
||||
|
||||
baseFrame := image.NewRGBA(avatarPNG.Bounds())
|
||||
draw.Draw(baseFrame, baseFrame.Bounds(), image.Black, image.Point{}, draw.Src)
|
||||
draw.Copy(baseFrame, image.Point{}, avatarPNG, avatarPNG.Bounds(), draw.Over, nil)
|
||||
draw.Draw(baseFrame, baseFrame.Bounds(), mainConfig.LogoImageBack, image.Point{-35, -35}, draw.Over)
|
||||
|
||||
anim := webpanimation.NewWebpAnimation(150, 150, 0)
|
||||
defer anim.ReleaseMemory()
|
||||
webpConf := webpanimation.NewWebpConfig()
|
||||
webpConf.SetLossless(1)
|
||||
|
||||
count := 20
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
frame := image.NewRGBA(baseFrame.Bounds())
|
||||
draw.Copy(frame, image.Point{}, baseFrame, baseFrame.Bounds(), draw.Src, nil)
|
||||
|
||||
var alpha uint8
|
||||
if i < count/2 {
|
||||
alpha = uint8(255. * (1. - float32(2*i)/float32(count)))
|
||||
} else {
|
||||
alpha = uint8(255. * (float32(2*i)/float32(count) - 1.))
|
||||
}
|
||||
|
||||
mask := image.NewUniform(color.Alpha{alpha})
|
||||
draw.DrawMask(frame, frame.Bounds(), mainConfig.LogoImageFront, image.Point{-35, -35}, mask, image.Point{}, draw.Over)
|
||||
|
||||
if err := anim.AddFrame(frame, 1000/count*i, webpConf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
outBuf, _ := os.Create(u.GetWebPAvatarPath())
|
||||
defer outBuf.Close()
|
||||
anim.Encode(outBuf)
|
||||
|
||||
imagick.Initialize()
|
||||
defer imagick.Terminate()
|
||||
|
||||
if _, err := imagick.ConvertImageCommand([]string{"convert", u.GetWebPAvatarPath(), u.GetGIFAvatarPath()}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.ReadFile(u.GetGIFAvatarPath())
|
||||
}
|
||||
|
||||
func (u *AudonUser) GetOriginalAvatarPath(hash [sha256.Size]byte, mtype *mimetype.MIME) string {
|
||||
filename := fmt.Sprintf("%x%s", hash, mtype.Extension())
|
||||
return u.getAvatarImagePath(filename)
|
||||
}
|
||||
|
||||
func (u *AudonUser) GetGIFAvatarPath() string {
|
||||
return u.getAvatarImagePath("indicator.gif")
|
||||
}
|
||||
|
||||
func (u *AudonUser) GetWebPAvatarPath() string {
|
||||
return u.getAvatarImagePath("indicator.webp")
|
||||
}
|
||||
|
||||
func (u *AudonUser) getAvatarImagePath(name string) string {
|
||||
if u == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return filepath.Join(mainConfig.StorageDir, u.AudonID, "avatar", name)
|
||||
}
|
44
config.go
44
config.go
|
@ -1,8 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/png"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
@ -18,8 +21,11 @@ type (
|
|||
}
|
||||
|
||||
AppConfigBase struct {
|
||||
LocalDomain string `validate:"required,hostname|hostname_port"`
|
||||
Environment string `validate:"printascii"`
|
||||
LocalDomain string `validate:"required,hostname|hostname_port"`
|
||||
Environment string `validate:"printascii"`
|
||||
StorageDir string
|
||||
LogoImageBack image.Image
|
||||
LogoImageFront image.Image
|
||||
}
|
||||
|
||||
LivekitConfig struct {
|
||||
|
@ -78,9 +84,39 @@ func loadConfig(envname string) (*AppConfig, error) {
|
|||
var appConf AppConfig
|
||||
|
||||
// Setup base config
|
||||
storageDir, err := filepath.Abs("public/storage")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.MkdirAll(storageDir, 0775); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
publicDir, _ := filepath.Abs("public")
|
||||
logoBack, err := os.Open(filepath.Join(publicDir, "logo_back.png"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer logoBack.Close()
|
||||
logoBackPng, err := png.Decode(logoBack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logoFront, err := os.Open(filepath.Join(publicDir, "logo_front.png"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer logoFront.Close()
|
||||
logoFrontPng, err := png.Decode(logoFront)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
basicConf := AppConfigBase{
|
||||
LocalDomain: os.Getenv("LOCAL_DOMAIN"),
|
||||
Environment: envname,
|
||||
LocalDomain: os.Getenv("LOCAL_DOMAIN"),
|
||||
Environment: envname,
|
||||
StorageDir: storageDir,
|
||||
LogoImageBack: logoBackPng,
|
||||
LogoImageFront: logoFrontPng,
|
||||
}
|
||||
if err := mainValidator.Struct(&basicConf); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -29,7 +29,7 @@ services:
|
|||
- "127.0.0.1:6379:6379"
|
||||
volumes:
|
||||
- ./redis:/data
|
||||
- ./config/redis.conf:/etc/redis.conf
|
||||
- ./config/redis.conf:/etc/redis.conf:ro
|
||||
|
||||
livekit:
|
||||
image: livekit/livekit-server:v1.3
|
||||
|
@ -39,7 +39,7 @@ services:
|
|||
depends_on:
|
||||
- redis
|
||||
volumes:
|
||||
- ./config/livekit.yaml:/etc/livekit.yaml
|
||||
- ./config/livekit.yaml:/etc/livekit.yaml:ro
|
||||
|
||||
audon:
|
||||
build: .
|
||||
|
|
7
go.mod
7
go.mod
|
@ -17,8 +17,11 @@ require (
|
|||
github.com/oklog/ulid/v2 v2.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rbcervilla/redisstore/v9 v9.0.0-rc1
|
||||
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882
|
||||
go.mongodb.org/mongo-driver v1.11.0
|
||||
golang.org/x/text v0.4.0
|
||||
golang.org/x/image v0.3.0
|
||||
golang.org/x/text v0.6.0
|
||||
gopkg.in/gographics/imagick.v2 v2.6.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
|
@ -30,6 +33,7 @@ require (
|
|||
github.com/eapache/channels v1.1.0 // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/frostbyte73/go-throttle v0.0.0-20210621200530-8018c891361d // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.2.3 // indirect
|
||||
|
@ -43,6 +47,7 @@ require (
|
|||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/jellydator/ttlcache/v3 v3.0.1 // indirect
|
||||
github.com/jxskiss/base62 v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/labstack/gommon v0.4.0 // indirect
|
||||
|
|
14
go.sum
14
go.sum
|
@ -76,6 +76,8 @@ github.com/frostbyte73/go-throttle v0.0.0-20210621200530-8018c891361d/go.mod h1:
|
|||
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/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
|
||||
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
|
||||
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=
|
||||
|
@ -184,6 +186,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
|
|||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jaevor/go-nanoid v1.3.0 h1:nD+iepesZS6pr3uOVf20vR9GdGgJW1HPaR46gtrxzkg=
|
||||
github.com/jaevor/go-nanoid v1.3.0/go.mod h1:SI+jFaPuddYkqkVQoNGHs81navCtH388TcrH0RqFKgY=
|
||||
github.com/jellydator/ttlcache/v3 v3.0.1 h1:cHgCSMS7TdQcoprXnWUptJZzyFsqs18Lt8VVhRuZYVU=
|
||||
github.com/jellydator/ttlcache/v3 v3.0.1/go.mod h1:WwTaEmcXQ3MTjOm4bsZoDFiCu/hMvNWLO1w67RXz6h4=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
|
@ -349,6 +353,8 @@ github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZ
|
|||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 h1:A7o8tOERTtpD/poS+2VoassCjXpjHn916luXbf5QKD0=
|
||||
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882/go.mod h1:5IwJoz9Pw7JsrCN4/skkxUtSWT7myuUPLhCgv6Q5vvQ=
|
||||
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=
|
||||
|
@ -425,6 +431,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
|||
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/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
|
||||
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -486,6 +494,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
|||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
|
@ -576,8 +585,9 @@ 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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=
|
||||
|
@ -718,6 +728,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
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/gographics/imagick.v2 v2.6.2 h1:8ILTJzDKQKSYSfav+9GZs9H8zOOR2UtZVTWkUdFoiZ8=
|
||||
gopkg.in/gographics/imagick.v2 v2.6.2/go.mod h1:/QVPLV/iKdNttRKthmDkeeGg+vdHurVEPc8zkU0XgBk=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
|
|
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 5.9 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 16 KiB |
91
room.go
91
room.go
|
@ -2,12 +2,19 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/jaevor/go-nanoid"
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/livekit"
|
||||
|
@ -262,7 +269,8 @@ func joinRoomHandler(c echo.Context) (err error) {
|
|||
return c.String(http.StatusForbidden, string(room.Restriction))
|
||||
}
|
||||
if !canTalk && (room.IsFollowingOnly() || room.IsFollowerOnly() || room.IsFollowingOrFollowerOnly() || room.IsMutualOnly()) {
|
||||
mastoClient, _ := getMastodonClient(c)
|
||||
data, _ := getSessionData(c)
|
||||
mastoClient := getMastodonClient(data)
|
||||
if mastoClient == nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
|
@ -314,9 +322,55 @@ func joinRoomHandler(c echo.Context) (err error) {
|
|||
}
|
||||
|
||||
resp := &TokenResponse{
|
||||
Url: mainConfig.Livekit.URL.String(),
|
||||
Token: token,
|
||||
AudonID: user.AudonID,
|
||||
Url: mainConfig.Livekit.URL.String(),
|
||||
Token: token,
|
||||
Audon: user,
|
||||
}
|
||||
if user.AvatarFile != "" {
|
||||
orig, err := os.ReadFile(user.getAvatarImagePath(user.AvatarFile))
|
||||
if err == nil && orig != nil {
|
||||
resp.Original = fmt.Sprintf("data:%s;base64,%s", mimetype.Detect(orig), base64.StdEncoding.EncodeToString(orig))
|
||||
}
|
||||
}
|
||||
|
||||
avatarLink := c.FormValue("avatar")
|
||||
if avatarLink != "" {
|
||||
avatarURL, err := url.Parse(avatarLink)
|
||||
if err != nil {
|
||||
return ErrInvalidRequestFormat
|
||||
}
|
||||
|
||||
if online, err := user.InLivekit(c.Request().Context()); !online && err == nil {
|
||||
// Download user's avatar
|
||||
req, err := http.NewRequest(http.MethodGet, avatarURL.String(), nil)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return ErrInvalidRequestFormat
|
||||
}
|
||||
req.Header.Set("User-Agent", USER_AGENT)
|
||||
|
||||
avatarResp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return ErrInvalidRequestFormat
|
||||
}
|
||||
defer avatarResp.Body.Close()
|
||||
|
||||
fnew, err := io.ReadAll(avatarResp.Body)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
indicator, err := user.GetIndicator(c.Request().Context(), fnew)
|
||||
if err != nil {
|
||||
c.Logger().Warn(err)
|
||||
}
|
||||
resp.Indicator = fmt.Sprintf("data:image/gif;base64,%s", base64.StdEncoding.EncodeToString(indicator))
|
||||
resp.Original = fmt.Sprintf("data:%s;base64,%s", mimetype.Detect(fnew), base64.StdEncoding.EncodeToString(fnew))
|
||||
} else if err != nil {
|
||||
c.Logger().Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create room in LiveKit if it doesn't exist
|
||||
|
@ -340,6 +394,10 @@ func joinRoomHandler(c echo.Context) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Store user's session data in cache
|
||||
data, _ := getSessionData(c)
|
||||
roomSessionCache.Set(user.AudonID, data, ttlcache.DefaultTTL)
|
||||
|
||||
return c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
|
@ -378,6 +436,23 @@ func closeRoomHandler(c echo.Context) error {
|
|||
return c.NoContent(http.StatusOK)
|
||||
}
|
||||
|
||||
// Client notifies server that user left room
|
||||
func leaveRoomHandler(c echo.Context) error {
|
||||
user := c.Get("user").(*AudonUser)
|
||||
still, err := user.InLivekit(c.Request().Context())
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
} else if still {
|
||||
return c.NoContent(http.StatusAccepted)
|
||||
}
|
||||
if err := user.ClearUserAvatar(c.Request().Context()); err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
return c.NoContent(http.StatusOK)
|
||||
}
|
||||
|
||||
func updatePermissionHandler(c echo.Context) error {
|
||||
roomID := c.Param("room")
|
||||
|
||||
|
@ -399,11 +474,11 @@ func updatePermissionHandler(c echo.Context) error {
|
|||
return ErrOperationNotPermitted
|
||||
}
|
||||
|
||||
tgtAudonID := c.Param("user")
|
||||
if !lkRoomMetadata.IsUserInLivekitRoom(c.Request().Context(), tgtAudonID) {
|
||||
audonID := c.Param("user")
|
||||
if !lkRoomMetadata.IsUserInLivekitRoom(c.Request().Context(), audonID) {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
tgtUser, err := findUserByID(c.Request().Context(), tgtAudonID)
|
||||
tgtUser, err := findUserByID(c.Request().Context(), audonID)
|
||||
if err != nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
@ -443,7 +518,7 @@ func updatePermissionHandler(c echo.Context) error {
|
|||
|
||||
info, err := lkRoomServiceClient.UpdateParticipant(c.Request().Context(), &livekit.UpdateParticipantRequest{
|
||||
Room: roomID,
|
||||
Identity: tgtAudonID,
|
||||
Identity: audonID,
|
||||
Permission: newPermission,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
48
schema.go
48
schema.go
|
@ -20,11 +20,12 @@ type (
|
|||
}
|
||||
|
||||
AudonUser struct {
|
||||
AudonID string `bson:"audon_id" json:"audon_id" validate:"alphanum"`
|
||||
RemoteID string `bson:"remote_id" json:"remote_id" validate:"printascii"`
|
||||
RemoteURL string `bson:"remote_url" json:"remote_url" validate:"url"`
|
||||
Webfinger string `bson:"webfinger" json:"webfinger" validate:"email"`
|
||||
CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
||||
AudonID string `bson:"audon_id" json:"audon_id" validate:"alphanum"`
|
||||
RemoteID string `bson:"remote_id" json:"remote_id" validate:"printascii"`
|
||||
RemoteURL string `bson:"remote_url" json:"remote_url" validate:"url"`
|
||||
Webfinger string `bson:"webfinger" json:"webfinger" validate:"email"`
|
||||
AvatarFile string `bson:"avatar" json:"avatar"`
|
||||
CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
RoomMetadata struct {
|
||||
|
@ -47,9 +48,11 @@ type (
|
|||
}
|
||||
|
||||
TokenResponse struct {
|
||||
Url string `json:"url"`
|
||||
Token string `json:"token"`
|
||||
AudonID string `json:"audon_id"`
|
||||
Url string `json:"url"`
|
||||
Token string `json:"token"`
|
||||
Audon *AudonUser `json:"audon"`
|
||||
Indicator string `json:"indicator"`
|
||||
Original string `json:"original"`
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -67,12 +70,28 @@ const (
|
|||
PRIVATE JoinRestriction = "private"
|
||||
)
|
||||
|
||||
func (a *AudonUser) Equal(u *AudonUser) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
func (a *AudonUser) GetCurrentLivekitRooms(ctx context.Context) ([]*livekit.Room, error) {
|
||||
resp, err := lkRoomServiceClient.ListRooms(ctx, &livekit.ListRoomsRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.AudonID == u.AudonID || (a.RemoteID == u.RemoteID && a.RemoteURL == u.RemoteURL)
|
||||
rooms := resp.GetRooms()
|
||||
current := []*livekit.Room{}
|
||||
for _, r := range rooms {
|
||||
partResp, err := lkRoomServiceClient.ListParticipants(ctx, &livekit.ListParticipantsRequest{
|
||||
Room: r.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, p := range partResp.GetParticipants() {
|
||||
if p.Identity == a.AudonID {
|
||||
current = append(current, r)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func (r *Room) IsFollowingOnly() bool {
|
||||
|
@ -170,6 +189,9 @@ func createIndexes(ctx context.Context) error {
|
|||
{Key: "remote_id", Value: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{{Key: "webfinger", Value: 1}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
28
server.go
28
server.go
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/go-playground/validator/v10"
|
||||
"github.com/go-redis/redis/v9"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
"github.com/labstack/echo/v4"
|
||||
lksdk "github.com/livekit/server-sdk-go"
|
||||
|
@ -52,6 +53,8 @@ var (
|
|||
mainConfig *AppConfig
|
||||
lkRoomServiceClient *lksdk.RoomServiceClient
|
||||
localeBundle *i18n.Bundle
|
||||
roomSessionCache *ttlcache.Cache[string, *SessionData]
|
||||
webhookTimerCache *ttlcache.Cache[string, *time.Timer]
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -150,6 +153,12 @@ func main() {
|
|||
redisStore.Options(sessionOptions)
|
||||
e.Use(session.Middleware(redisStore))
|
||||
|
||||
// Setup caches
|
||||
roomSessionCache = ttlcache.New(ttlcache.WithTTL[string, *SessionData](24 * time.Hour))
|
||||
webhookTimerCache = ttlcache.New(ttlcache.WithTTL[string, *time.Timer](60 * time.Second))
|
||||
go roomSessionCache.Start()
|
||||
go webhookTimerCache.Start()
|
||||
|
||||
e.POST("/app/login", loginHandler)
|
||||
e.GET("/app/oauth", oauthHandler)
|
||||
e.GET("/app/verify", verifyHandler)
|
||||
|
@ -160,14 +169,17 @@ func main() {
|
|||
|
||||
api := e.Group("/api", authMiddleware)
|
||||
api.GET("/token", getOAuthTokenHandler)
|
||||
api.GET("/user/:id", getUserHandler)
|
||||
api.POST("/room", createRoomHandler)
|
||||
api.GET("/room/:id", joinRoomHandler)
|
||||
api.DELETE("/room", leaveRoomHandler)
|
||||
api.POST("/room/:id", joinRoomHandler)
|
||||
api.PATCH("/room/:id", updateRoomHandler)
|
||||
api.DELETE("/room/:id", closeRoomHandler)
|
||||
api.PUT("/room/:room/:user", updatePermissionHandler)
|
||||
|
||||
e.Static("/assets", "audon-fe/dist/assets")
|
||||
e.Static("/static", "audon-fe/dist/static")
|
||||
e.Static("/storage", mainConfig.StorageDir)
|
||||
e.GET("/r/:id", renderRoomHandler)
|
||||
e.GET("/*", func(c echo.Context) error {
|
||||
return c.Render(http.StatusOK, "tmpl", &TemplateData{Config: &mainConfig.AppConfigBase})
|
||||
|
@ -189,6 +201,8 @@ func main() {
|
|||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
e.Logger.Print("Attempting graceful shutdown")
|
||||
defer shutdownCancel()
|
||||
roomSessionCache.DeleteAll()
|
||||
webhookTimerCache.DeleteAll()
|
||||
if err := e.Shutdown(shutdownCtx); err != nil {
|
||||
e.Logger.Fatalf("Failed shutting down gracefully: %s\n", err.Error())
|
||||
}
|
||||
|
@ -206,16 +220,6 @@ func (cv *CustomValidator) Validate(i interface{}) error {
|
|||
}
|
||||
|
||||
func getAppConfig(server string, redirPath string) (*mastodon.AppConfig, error) {
|
||||
// if mastAppConfigBase != nil {
|
||||
// return &mastodon.AppConfig{
|
||||
// Server: server,
|
||||
// ClientName: mastAppConfigBase.ClientName,
|
||||
// Scopes: mastAppConfigBase.Scopes,
|
||||
// Website: mastAppConfigBase.Website,
|
||||
// RedirectURIs: mastAppConfigBase.RedirectURIs,
|
||||
// }, nil
|
||||
// }
|
||||
|
||||
if redirPath == "" {
|
||||
redirPath = "/"
|
||||
}
|
||||
|
@ -233,7 +237,7 @@ func getAppConfig(server string, redirPath string) (*mastodon.AppConfig, error)
|
|||
|
||||
conf := &mastodon.AppConfig{
|
||||
ClientName: "Audon",
|
||||
Scopes: "read:accounts read:follows",
|
||||
Scopes: "read:accounts read:follows write:accounts",
|
||||
Website: "https://codeberg.org/nmkj/audon",
|
||||
RedirectURIs: redirectURI,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func getUserHandler(c echo.Context) error {
|
||||
audonID := c.Param("id")
|
||||
if err := mainValidator.Var(&audonID, "required,printascii"); err != nil {
|
||||
return wrapValidationError(err)
|
||||
}
|
||||
|
||||
user, err := findUserByID(c.Request().Context(), audonID)
|
||||
if err != nil {
|
||||
return ErrUserNotFound
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
|
||||
func (a *AudonUser) Equal(u *AudonUser) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return a.AudonID == u.AudonID || (a.RemoteID == u.RemoteID && a.RemoteURL == u.RemoteURL)
|
||||
}
|
||||
|
||||
func (a *AudonUser) InLivekit(ctx context.Context) (bool, error) {
|
||||
rooms, err := a.GetCurrentLivekitRooms(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(rooms) > 0, nil
|
||||
}
|
||||
|
||||
func (a *AudonUser) ClearUserAvatar(ctx context.Context) error {
|
||||
coll := mainDB.Collection(COLLECTION_USER)
|
||||
_, err := coll.UpdateOne(ctx,
|
||||
bson.D{{Key: "audon_id", Value: a.AudonID}},
|
||||
bson.D{
|
||||
{Key: "$set", Value: bson.D{{Key: "avatar", Value: ""}}},
|
||||
})
|
||||
return err
|
||||
}
|
67
utils.go
67
utils.go
|
@ -1,15 +1,20 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
mastodon "github.com/mattn/go-mastodon"
|
||||
)
|
||||
|
||||
|
@ -73,13 +78,65 @@ func registerApp(ctx context.Context, appConfig *mastodon.AppConfig) (*mastodon.
|
|||
return &app, nil
|
||||
}
|
||||
|
||||
func getMastodonClient(c echo.Context) (*mastodon.Client, error) {
|
||||
data, err := getSessionData(c)
|
||||
if err != nil || data.MastodonConfig.AccessToken == "" {
|
||||
// Updates the avatar of the current user.
|
||||
func updateAvatar(ctx context.Context, c *mastodon.Client, filename string) (*mastodon.Account, error) {
|
||||
u, err := url.Parse(c.Config.Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = path.Join(u.Path, "/api/v1/accounts/update_credentials")
|
||||
|
||||
avatar, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
mw := multipart.NewWriter(buf)
|
||||
|
||||
// h := make(textproto.MIMEHeader)
|
||||
// h.Set("Content-Disposition", "form-data; name=\"avatar\"; filename=\"blob\"")
|
||||
// h.Set("Content-Type", mimetype.Detect(avatar).String())
|
||||
// part, err := mw.CreatePart(h)
|
||||
part, err := mw.CreateFormFile("avatar", filepath.Base(filename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
io.Copy(part, avatar)
|
||||
mw.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPatch, u.String(), buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.Header.Set("Authorization", "Bearer "+c.Config.AccessToken)
|
||||
req.Header.Set("Content-Type", mw.FormDataContentType())
|
||||
c.UserAgent = USER_AGENT
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("request failed: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
account := new(mastodon.Account)
|
||||
err = json.NewDecoder(resp.Body).Decode(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func getMastodonClient(data *SessionData) *mastodon.Client {
|
||||
if data == nil || data.MastodonConfig.AccessToken == "" {
|
||||
return nil
|
||||
}
|
||||
mastoClient := mastodon.NewClient(data.MastodonConfig)
|
||||
mastoClient.UserAgent = USER_AGENT
|
||||
|
||||
return mastoClient, nil
|
||||
return mastoClient
|
||||
}
|
||||
|
|
65
webhooks.go
65
webhooks.go
|
@ -2,14 +2,19 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jellydator/ttlcache/v3"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/livekit/protocol/auth"
|
||||
"github.com/livekit/protocol/webhook"
|
||||
mastodon "github.com/mattn/go-mastodon"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func livekitWebhookHandler(c echo.Context) error {
|
||||
|
@ -32,6 +37,66 @@ func livekitWebhookHandler(c echo.Context) error {
|
|||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
} else if event.GetEvent() == webhook.EventParticipantLeft {
|
||||
// Revert user's avatar
|
||||
audonID := event.GetParticipant().GetIdentity()
|
||||
user, err := findUserByID(c.Request().Context(), audonID)
|
||||
if user == nil || err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusNotFound)
|
||||
}
|
||||
still, err := user.InLivekit(c.Request().Context())
|
||||
if !still && err == nil {
|
||||
data := roomSessionCache.Get(audonID)
|
||||
if data == nil {
|
||||
return echo.NewHTTPError(http.StatusGone)
|
||||
}
|
||||
roomSessionCache.Delete(audonID)
|
||||
mastoClient := getMastodonClient(data.Value())
|
||||
if mastoClient == nil {
|
||||
c.Logger().Errorf("unable to get mastodon client: %v", data.Value().MastodonConfig)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
cached := webhookTimerCache.Get(audonID)
|
||||
if cached != nil {
|
||||
oldTimer := cached.Value()
|
||||
if !oldTimer.Stop() {
|
||||
<-oldTimer.C
|
||||
}
|
||||
}
|
||||
countdown := time.NewTimer(10 * time.Second)
|
||||
webhookTimerCache.Set(audonID, countdown, ttlcache.DefaultTTL)
|
||||
|
||||
<-countdown.C
|
||||
webhookTimerCache.Delete(audonID)
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
// defer cancel()
|
||||
ctx := context.TODO()
|
||||
|
||||
stillAgain, err := user.InLivekit(ctx)
|
||||
if stillAgain || err != nil {
|
||||
return c.NoContent(http.StatusOK)
|
||||
}
|
||||
user, err = findUserByID(ctx, audonID)
|
||||
if err == nil && user.AvatarFile != "" {
|
||||
log.Printf("restoring avatar: %s\n", audonID)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
avatar := user.getAvatarImagePath(user.AvatarFile)
|
||||
_, err = updateAvatar(ctx, mastoClient, avatar)
|
||||
if err != nil {
|
||||
c.Logger().Error(err)
|
||||
}
|
||||
user.ClearUserAvatar(ctx)
|
||||
os.Remove(avatar)
|
||||
} else if err != nil {
|
||||
c.Logger().Error(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
return c.NoContent(http.StatusOK)
|
||||
} else if event.GetEvent() == webhook.EventRoomStarted {
|
||||
// Have the bot advertise the room
|
||||
room, err := findRoomByID(c.Request().Context(), event.GetRoom().GetName())
|
||||
|
|
Ładowanie…
Reference in New Issue