add gif indicator

peertube
Namekuji 2023-01-23 07:10:21 -05:00
rodzic 3469d62210
commit a91d7d340a
29 zmienionych plików z 942 dodań i 252 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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": {

Wyświetl plik

@ -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

Wyświetl plik

@ -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

1
.dockerignore 100644
Wyświetl plik

@ -0,0 +1 @@
public/storage/

1
.gitignore vendored
Wyświetl plik

@ -189,3 +189,4 @@ config/*
mongo/
redis/
cache/
public/storage

2
.vscode/launch.json vendored
Wyświetl plik

@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"name": "Launch Go",
"type": "go",
"request": "launch",
"mode": "auto",

Wyświetl plik

@ -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

319
audon-fe/package-lock.json wygenerowano
Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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>

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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());
},
},
});

Wyświetl plik

@ -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>

Wyświetl plik

@ -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

Wyświetl plik

@ -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,
})
}

158
avatar.go 100644
Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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
Wyświetl plik

@ -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
Wyświetl plik

@ -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
Wyświetl plik

@ -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 {

Wyświetl plik

@ -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

Wyświetl plik

@ -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,
}

50
user.go 100644
Wyświetl plik

@ -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
}

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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())