peertube
Namekuji 2023-01-26 13:28:25 -05:00
rodzic 073510d4b5
commit 17e07d28a1
16 zmienionych plików z 209 dodań i 60 usunięć

Wyświetl plik

@ -2,7 +2,7 @@ audon.localhost {
tls /etc/caddy/certs/cert.pem /etc/caddy/certs/key.pem tls /etc/caddy/certs/cert.pem /etc/caddy/certs/key.pem
encode zstd gzip encode zstd gzip
@backend { @backend {
path /app/* /api/* /storage/* path /app/* /api/* /storage/* /u/*
} }
handle @backend { handle @backend {
reverse_proxy devcontainer:8100 reverse_proxy devcontainer:8100

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

@ -1,12 +1,12 @@
{ {
"name": "audon-fe", "name": "audon-fe",
"version": "0.2.0", "version": "0.2.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "audon-fe", "name": "audon-fe",
"version": "0.2.0", "version": "0.2.3",
"dependencies": { "dependencies": {
"@intlify/unplugin-vue-i18n": "^0.8.1", "@intlify/unplugin-vue-i18n": "^0.8.1",
"@picmo/popup-picker": "^5.7.2", "@picmo/popup-picker": "^5.7.2",
@ -809,9 +809,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.8.1", "version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -895,9 +895,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.2.3", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.3.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.5.tgz",
"integrity": "sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==", "integrity": "sha512-9pU/8mmjSSOb4CXVsvGIevN+MlO/t9OWtKadTaLuN85Gge3HGorUckgp8A/2FH4V4hJ7JuQ3LIeI7KAV9ITZrQ==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
@ -2342,9 +2342,9 @@
} }
}, },
"node_modules/livekit-client": { "node_modules/livekit-client": {
"version": "1.6.2", "version": "1.6.3",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-1.6.2.tgz", "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-1.6.3.tgz",
"integrity": "sha512-OJIcBY8fzS8tf4vvBL/LNjmEHMFRLivnRjBDjbSiJ8nqV7UB7iSN40RMAejp/OPjhB5ys1u8tt6m3RIXrG2TJA==", "integrity": "sha512-hnVN/rQ9pVWK0L1lemLSOzBI6IXoJepgHP/USqmGJ6eucaoLMx1jK0iMWpR5T2/pg97GmZaUaRSUcXlELNSsoA==",
"dependencies": { "dependencies": {
"async-await-queue": "^1.2.1", "async-await-queue": "^1.2.1",
"events": "^3.3.0", "events": "^3.3.0",
@ -2465,9 +2465,9 @@
} }
}, },
"node_modules/masto": { "node_modules/masto": {
"version": "5.6.0", "version": "5.6.1",
"resolved": "https://registry.npmjs.org/masto/-/masto-5.6.0.tgz", "resolved": "https://registry.npmjs.org/masto/-/masto-5.6.1.tgz",
"integrity": "sha512-+5t6bSt/V0HyL3hDowxabVszfLjkrKVunhgWXC4LrCBMjMKIOF4vrFJriXSPKOprB6VCZztvGGuy02zSy7oRcQ==", "integrity": "sha512-mQxuOLCWP0TSswZEmmrbHbV2+Olt2N+lr1V2C3FO0QCrPGAhx3KLAXRLArajeGtcVtu66/k8gtTWKksYfkQu5Q==",
"dependencies": { "dependencies": {
"@mastojs/ponyfills": "^1.0.4", "@mastojs/ponyfills": "^1.0.4",
"change-case": "^4.1.2", "change-case": "^4.1.2",
@ -2963,9 +2963,9 @@
} }
}, },
"node_modules/protobufjs": { "node_modules/protobufjs": {
"version": "7.1.2", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.0.tgz",
"integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "integrity": "sha512-hYCqTDuII4iJ4stZqiuGCSU8xxWl5JeXYpwARGtn/tWcKCAro6h3WQz+xpsNbXW0UYqpmTQFEyFWO0G0Kjt64g==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@protobufjs/aspromise": "^1.1.2", "@protobufjs/aspromise": "^1.1.2",
@ -3365,24 +3365,10 @@
"rxjs": "*" "rxjs": "*"
} }
}, },
"node_modules/typescript": {
"version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
"optional": true,
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/ua-parser-js": { "node_modules/ua-parser-js": {
"version": "1.0.32", "version": "1.0.33",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.33.tgz",
"integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", "integrity": "sha512-RqshF7TPTE0XLYAqmjlu5cLLuGdKrNu9O1KLA/qp39QtbZwuzwv1dT46DZSopoUMsYgXpB3Cv8a03FI8b74oFQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",

Wyświetl plik

@ -46,6 +46,7 @@ roomReady:
header: "Your room is ready!" header: "Your room is ready!"
message: "Your room \"{title}\" is now ready. Share the following URL with other participants." message: "Your room \"{title}\" is now ready. Share the following URL with other participants."
errors: errors:
offline: "This user is offline now."
invalidAddress: "Invalid address" invalidAddress: "Invalid address"
serverNotFound: "Instance not found" serverNotFound: "Instance not found"
notFound: "{value} not found" notFound: "{value} not found"

Wyświetl plik

@ -46,6 +46,7 @@ roomReady:
header: "お部屋の用意ができました!" header: "お部屋の用意ができました!"
message: "{title} を作りました。参加者に以下の URL を共有してください。" message: "{title} を作りました。参加者に以下の URL を共有してください。"
errors: errors:
offline: "このユーザーは現在オフラインです。"
invalidAddress: "アドレスが有効ではありません" invalidAddress: "アドレスが有効ではありません"
serverNotFound: "サーバーが見つかりません" serverNotFound: "サーバーが見つかりません"
notFound: "{value} が見つかりません" notFound: "{value} が見つかりません"

Wyświetl plik

@ -1,7 +1,8 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import LoginView from "../views/LoginView.vue"; import LoginView from "../views/LoginView.vue";
import RoomView from "../views/RoomView.vue"; import RoomView from "../views/RoomView.vue";
import NotFoundView from "../views/NotFoundView.vue"; import ErrorView from "../views/ErrorView.vue";
import axios from "axios";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -12,7 +13,15 @@ const router = createRouter({
meta: { meta: {
noauth: true, noauth: true,
}, },
component: NotFoundView, component: ErrorView,
},
{
path: "/error/:type",
name: "error",
meta: {
noauth: true,
},
component: ErrorView,
}, },
{ {
path: "/", path: "/",
@ -40,6 +49,27 @@ const router = createRouter({
}, },
component: RoomView, component: RoomView,
}, },
{
path: "/u/:webfinger",
name: "currentHosting",
meta: {
noauth: true,
},
redirect: async (to) => {
try {
const resp = await axios.get(`/u/${to.params.webfinger}`);
if (resp.status === 302) {
return {
name: "room",
params: { id: resp.headers.location },
};
}
} catch (error) {
console.log(error);
}
return { name: "notfound" };
},
},
], ],
}); });

Wyświetl plik

@ -32,7 +32,15 @@ export default {
clipboard: useClipboard(), clipboard: useClipboard(),
}; };
}, },
created() { async created() {
const resp = await axios.get("/api/room");
if (resp.data.length > 0) {
const canCreate = !some(resp.data, { role: "host" });
if (!canCreate) {
alert(this.$t("errors.alreadyAdded"));
this.$router.replace({ name: "home" });
}
}
this.cohostSearch = debounce(this.search, 1000); this.cohostSearch = debounce(this.search, 1000);
}, },
unmounted() { unmounted() {

Wyświetl plik

@ -0,0 +1,23 @@
<script>
export default {
data() {
return {
name: this.$route.name,
};
},
created() {
const type = this.$route.params.type;
if (!type) return;
if (type === "offline") {
const target = new URL(decodeURI(this.$route.query.url));
alert(this.$t("errors.offline"));
window.location.href = target.toString();
}
},
};
</script>
<template>
<v-alert v-if="this.name === 'notfound'" type="error">Page not found</v-alert>
</template>

Wyświetl plik

@ -1,6 +1,7 @@
<script> <script>
import { useMastodonStore } from "../stores/mastodon"; import { useMastodonStore } from "../stores/mastodon";
import axios from "axios"; import axios from "axios";
import { some } from "lodash-es";
export default { export default {
setup() { setup() {
@ -10,9 +11,16 @@ export default {
}, },
data() { data() {
return { return {
canCreate: true,
query: "", query: "",
}; };
}, },
async created() {
const resp = await axios.get("/api/room");
if (resp.data.length > 0) {
this.canCreate = !some(resp.data, { role: "host" });
}
},
methods: { methods: {
async onLogout() { async onLogout() {
// if (!confirm(this.$t("logoutConfirm"))) return; // if (!confirm(this.$t("logoutConfirm"))) return;
@ -63,9 +71,13 @@ export default {
<v-text-field v-mode="query"></v-text-field> <v-text-field v-mode="query"></v-text-field>
</v-col> --> </v-col> -->
<v-col cols="12"> <v-col cols="12">
<v-btn block :to="{ name: 'create' }" color="indigo">{{ <v-btn
$t("createNewRoom") :disabled="!canCreate"
}}</v-btn> block
:to="{ name: 'create' }"
color="indigo"
>{{ $t("createNewRoom") }}</v-btn
>
</v-col> </v-col>
</v-row> </v-row>
</main> </main>

Wyświetl plik

@ -1,5 +0,0 @@
<script></script>
<template>
<v-alert type="error">Page not found</v-alert>
</template>

Wyświetl plik

@ -557,11 +557,7 @@ export default {
return metadata; return metadata;
}, },
async fetchMastoData(identity) { async fetchMastoData(identity) {
if ( if (this.roomInfo.accounts[identity] === undefined) return;
this.cachedMastoData[identity] !== undefined ||
this.roomInfo.accounts[identity] === undefined
)
return;
try { try {
const resp = await axios.get(`/app/user/${identity}`); const resp = await axios.get(`/app/user/${identity}`);
const account = this.roomInfo.accounts[identity]; const account = this.roomInfo.accounts[identity];

Wyświetl plik

@ -174,7 +174,6 @@ func oauthHandler(c echo.Context) (err error) {
} }
return c.Redirect(http.StatusFound, req.Redirect) return c.Redirect(http.StatusFound, req.Redirect)
// return c.Redirect(http.StatusFound, "http://localhost:5173")
} }
func getUserTokenHandler(c echo.Context) (err error) { func getUserTokenHandler(c echo.Context) (err error) {

17
room.go
Wyświetl plik

@ -35,6 +35,23 @@ func createRoomHandler(c echo.Context) error {
host := c.Get("user").(*AudonUser) host := c.Get("user").(*AudonUser)
room.Host = host room.Host = host
// check if user is already hosting
lkRooms, err := host.GetCurrentLivekitRooms(c.Request().Context())
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
for _, r := range lkRooms {
meta, err := getRoomMetadataFromLivekitRoom(r)
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
if meta.Host.Equal(host) {
return ErrOperationNotPermitted
}
}
coll := mainDB.Collection(COLLECTION_ROOM) coll := mainDB.Collection(COLLECTION_ROOM)
now := time.Now().UTC() now := time.Now().UTC()

Wyświetl plik

@ -133,6 +133,15 @@ func (r *Room) IsHost(u *AudonUser) bool {
return r != nil && r.Host.Equal(u) return r != nil && r.Host.Equal(u)
} }
func (r *RoomMetadata) IsSpeaker(u *AudonUser) bool {
for _, s := range r.Speakers {
if s.Equal(u) {
return true
}
}
return false
}
func getRoomMetadataFromLivekitRoom(lkRoom *livekit.Room) (*RoomMetadata, error) { func getRoomMetadataFromLivekitRoom(lkRoom *livekit.Room) (*RoomMetadata, error) {
metadata := new(RoomMetadata) metadata := new(RoomMetadata)
if err := json.Unmarshal([]byte(lkRoom.GetMetadata()), metadata); err != nil { if err := json.Unmarshal([]byte(lkRoom.GetMetadata()), metadata); err != nil {
@ -240,3 +249,12 @@ func findUserByID(ctx context.Context, audonID string) (*AudonUser, error) {
} }
return &result, nil return &result, nil
} }
func findUserByWebfinger(ctx context.Context, webfinger string) (*AudonUser, error) {
var result AudonUser
coll := mainDB.Collection(COLLECTION_USER)
if err := coll.FindOne(ctx, bson.D{{Key: "webfinger", Value: webfinger}}).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}

Wyświetl plik

@ -182,6 +182,7 @@ func main() {
e.Static("/static", "audon-fe/dist/static") e.Static("/static", "audon-fe/dist/static")
e.Static("/storage", mainConfig.StorageDir) e.Static("/storage", mainConfig.StorageDir)
e.GET("/r/:id", renderRoomHandler) e.GET("/r/:id", renderRoomHandler)
e.GET("/u/:webfinger", redirectUserHandler)
e.GET("/*", func(c echo.Context) error { e.GET("/*", func(c echo.Context) error {
return c.Render(http.StatusOK, "tmpl", &TemplateData{Config: &mainConfig.AppConfigBase}) return c.Render(http.StatusOK, "tmpl", &TemplateData{Config: &mainConfig.AppConfigBase})
}) })

74
user.go
Wyświetl plik

@ -2,7 +2,9 @@ package main
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -51,13 +53,56 @@ func getUserHandler(c echo.Context) error {
func getStatusHandler(c echo.Context) error { func getStatusHandler(c echo.Context) error {
u := c.Get("user").(*AudonUser) u := c.Get("user").(*AudonUser)
ids, err := u.GetCurrentRoomIDs(c.Request().Context()) status, err := u.GetCurrentRoomStatus(c.Request().Context())
if err != nil { if err != nil {
c.Logger().Error(err) c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError) return echo.NewHTTPError(http.StatusInternalServerError)
} }
return c.JSON(http.StatusOK, ids) return c.JSON(http.StatusOK, status)
}
func redirectUserHandler(c echo.Context) error {
input := c.Param("webfinger")
if err := mainValidator.Var(&input, "required,startswith=@,min=4"); err != nil {
return wrapValidationError(err)
}
webfinger := input[1:]
if err := mainValidator.Var(&webfinger, "email"); err != nil {
return wrapValidationError(err)
}
user, err := findUserByWebfinger(c.Request().Context(), webfinger)
if err != nil || user == nil {
return ErrUserNotFound
}
rooms, err := user.GetCurrentLivekitRooms(c.Request().Context())
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
for _, r := range rooms {
meta, err := getRoomMetadataFromLivekitRoom(r)
if err != nil {
continue
}
if meta.Host.Equal(user) {
return c.Redirect(http.StatusFound, fmt.Sprintf("/r/%s", r.GetName()))
}
}
query := make(url.Values)
query.Add("url", user.RemoteURL)
result := url.URL{
Path: "/error/offline",
ForceQuery: true,
OmitHost: true,
RawQuery: query.Encode(),
}
return c.Redirect(http.StatusFound, result.String())
} }
func (a *AudonUser) Equal(u *AudonUser) bool { func (a *AudonUser) Equal(u *AudonUser) bool {
@ -90,14 +135,31 @@ func (a *AudonUser) ClearUserAvatar(ctx context.Context) error {
return err return err
} }
func (a *AudonUser) GetCurrentRoomIDs(ctx context.Context) ([]string, error) { type UserStatus struct {
RoomID string `json:"roomID"`
Role string `json:"role"`
}
func (a *AudonUser) GetCurrentRoomStatus(ctx context.Context) ([]UserStatus, error) {
rooms, err := a.GetCurrentLivekitRooms(ctx) rooms, err := a.GetCurrentLivekitRooms(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
roomIDs := make([]string, len(rooms)) roomList := make([]UserStatus, len(rooms))
for i, r := range rooms { for i, r := range rooms {
roomIDs[i] = r.GetName() meta, _ := getRoomMetadataFromLivekitRoom(r)
role := "listener"
if meta.Room.IsHost(a) {
role = "host"
} else if meta.Room.IsCoHost(a) {
role = "cohost"
} else if meta.IsSpeaker(a) {
role = "speaker"
}
roomList[i] = UserStatus{
RoomID: r.GetName(),
Role: role,
}
} }
return roomIDs, nil return roomList, nil
} }

Wyświetl plik

@ -64,13 +64,13 @@ func livekitWebhookHandler(c echo.Context) error {
<-oldTimer.C <-oldTimer.C
} }
} }
countdown := time.NewTimer(10 * time.Second) countdown := time.NewTimer(60 * time.Second)
webhookTimerCache.Set(audonID, countdown, ttlcache.DefaultTTL) webhookTimerCache.Set(audonID, countdown, ttlcache.DefaultTTL)
go func() { go func() {
<-countdown.C <-countdown.C
webhookTimerCache.Delete(audonID) webhookTimerCache.Delete(audonID)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
stillAgain, err := user.InLivekit(ctx) stillAgain, err := user.InLivekit(ctx)