kopia lustrzana https://codeberg.org/nmkj/audon
add role update
rodzic
55ca35c3c8
commit
cff4f9eaab
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "audon-fe",
|
"name": "audon-fe",
|
||||||
"version": "0.2.3",
|
"version": "0.2.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "audon-fe",
|
"name": "audon-fe",
|
||||||
"version": "0.2.3",
|
"version": "0.2.4",
|
||||||
"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",
|
||||||
|
|
|
@ -18,6 +18,7 @@ export default {
|
||||||
muted: Boolean,
|
muted: Boolean,
|
||||||
emoji: String,
|
emoji: String,
|
||||||
preview: Boolean,
|
preview: Boolean,
|
||||||
|
enableMenu: Boolean,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showEmoji() {
|
showEmoji() {
|
||||||
|
@ -34,17 +35,17 @@ export default {
|
||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case "host":
|
case "host":
|
||||||
return {
|
return {
|
||||||
content: "Host",
|
content: this.$t("role.host"),
|
||||||
colour: "deep-orange",
|
colour: "deep-orange",
|
||||||
};
|
};
|
||||||
case "cohost":
|
case "cohost":
|
||||||
return {
|
return {
|
||||||
content: "Cohost",
|
content: this.$t("role.cohost"),
|
||||||
colour: "indigo",
|
colour: "indigo",
|
||||||
};
|
};
|
||||||
case "speaker":
|
case "speaker":
|
||||||
return {
|
return {
|
||||||
content: "Speaker",
|
content: this.$t("role.speaker"),
|
||||||
colour: "",
|
colour: "",
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
|
@ -78,7 +79,11 @@ export default {
|
||||||
>
|
>
|
||||||
<span>{{ emoji }}</span>
|
<span>{{ emoji }}</span>
|
||||||
</v-overlay>
|
</v-overlay>
|
||||||
<v-img :src="data?.avatar"></v-img>
|
<v-img
|
||||||
|
:class="{ cursorPointer: enableMenu }"
|
||||||
|
:id="`mod-${data?.identity}`"
|
||||||
|
:src="data?.avatar"
|
||||||
|
></v-img>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
</v-badge>
|
</v-badge>
|
||||||
<v-avatar
|
<v-avatar
|
||||||
|
@ -97,8 +102,34 @@ export default {
|
||||||
>
|
>
|
||||||
<span>{{ emoji }}</span>
|
<span>{{ emoji }}</span>
|
||||||
</v-overlay>
|
</v-overlay>
|
||||||
<v-img :src="data?.avatar"></v-img>
|
<v-img
|
||||||
|
:class="{ cursorPointer: enableMenu }"
|
||||||
|
:id="`mod-${data?.identity}`"
|
||||||
|
:src="data?.avatar"
|
||||||
|
></v-img>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
|
<v-menu v-if="enableMenu" :activator="`#mod-${data?.identity}`">
|
||||||
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
:title="$t('moderation.promote', { role: $t('role.cohost') })"
|
||||||
|
@click="$emit('moderate', this.data?.identity, 'cohost')"
|
||||||
|
></v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
v-if="type !== 'speaker'"
|
||||||
|
:title="$t('moderation.promote', { role: $t('role.speaker') })"
|
||||||
|
@click="$emit('moderate', this.data?.identity, 'speaker')"
|
||||||
|
></v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
v-else
|
||||||
|
:title="$t('moderation.demote')"
|
||||||
|
@click="$emit('moderate', this.data?.identity, 'demote')"
|
||||||
|
></v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
:title="$t('moderation.kick')"
|
||||||
|
@click="$emit('moderate', this.data?.identity, 'kick')"
|
||||||
|
></v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
<h4 :class="canSpeak ? 'mt-1' : 'mt-2'">
|
<h4 :class="canSpeak ? 'mt-1' : 'mt-2'">
|
||||||
<v-icon
|
<v-icon
|
||||||
v-if="canSpeak && !preview"
|
v-if="canSpeak && !preview"
|
||||||
|
@ -121,4 +152,8 @@ export default {
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cursorPointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -25,6 +25,7 @@ editRoom: "Room Edit"
|
||||||
comingFuture: "Coming with future update!"
|
comingFuture: "Coming with future update!"
|
||||||
processing: "Processing now...<br />Keep this window open!"
|
processing: "Processing now...<br />Keep this window open!"
|
||||||
lostWarning: "Unsaved data will be lost if you leave the page, are you sure?"
|
lostWarning: "Unsaved data will be lost if you leave the page, are you sure?"
|
||||||
|
cannotUndone: "This process cannot be undone. Are you sure?"
|
||||||
staticLink:
|
staticLink:
|
||||||
title: "Your Audon Link"
|
title: "Your Audon Link"
|
||||||
hint: "Other participants can join to your room via this personal link while you're hosting."
|
hint: "Other participants can join to your room via this personal link while you're hosting."
|
||||||
|
@ -33,8 +34,8 @@ form:
|
||||||
titleRequired: "Room title required"
|
titleRequired: "Room title required"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
restriction: "Who can join"
|
restriction: "Who can join"
|
||||||
cohosts: "Cohosts"
|
cohosts: "CoHosts"
|
||||||
cohostCanAlwaysJoin: "Cohosts can join regardless of this setting."
|
cohostCanAlwaysJoin: "CoHosts can join regardless of this setting."
|
||||||
schedule: "Schedule at"
|
schedule: "Schedule at"
|
||||||
advertise: "Allow the bot ({bot}) to advertise your room"
|
advertise: "Allow the bot ({bot}) to advertise your room"
|
||||||
relationships:
|
relationships:
|
||||||
|
@ -43,7 +44,7 @@ form:
|
||||||
follower: "Followers-only"
|
follower: "Followers-only"
|
||||||
knowing: "Followed accounts and/or followers"
|
knowing: "Followed accounts and/or followers"
|
||||||
mutual: "Your mutuals"
|
mutual: "Your mutuals"
|
||||||
private: "Cohosts only"
|
private: "CoHosts only"
|
||||||
shareRoomMessage: "Join my Audon room!\n{link}\n\nTitle: {title}"
|
shareRoomMessage: "Join my Audon room!\n{link}\n\nTitle: {title}"
|
||||||
roomReady:
|
roomReady:
|
||||||
header: "Your room is ready!"
|
header: "Your room is ready!"
|
||||||
|
@ -82,6 +83,15 @@ speakRequest:
|
||||||
microphoneBlocked: "Your browser has blocked access to the microphone. Check permission settings of your device and browser."
|
microphoneBlocked: "Your browser has blocked access to the microphone. Check permission settings of your device and browser."
|
||||||
closeRoomConfirm: "Are you sure you want to close this room?"
|
closeRoomConfirm: "Are you sure you want to close this room?"
|
||||||
roomEvent:
|
roomEvent:
|
||||||
closedByHost: "Host has closed this room."
|
closedByHost: "This room has been closed."
|
||||||
removed: "You have been requested to leave."
|
removed: "You have been requested to leave."
|
||||||
disconnected: "Disconnected from this room."
|
disconnected: "Disconnected from this room."
|
||||||
|
moderation:
|
||||||
|
promote: "Promote to {role}"
|
||||||
|
demote: "Demote to listener"
|
||||||
|
kick: "Kick out"
|
||||||
|
role:
|
||||||
|
host: "Host"
|
||||||
|
cohost: "CoHost"
|
||||||
|
speaker: "Speaker"
|
||||||
|
listener: "Listener"
|
||||||
|
|
|
@ -25,6 +25,7 @@ editRoom: "部屋の編集"
|
||||||
comingFuture: "今後のアップデートで追加予定"
|
comingFuture: "今後のアップデートで追加予定"
|
||||||
processing: "処理中です。<br />画面を閉じないでください。"
|
processing: "処理中です。<br />画面を閉じないでください。"
|
||||||
lostWarning: "この画面を閉じると保存前の内容が失われます。構いませんか?"
|
lostWarning: "この画面を閉じると保存前の内容が失われます。構いませんか?"
|
||||||
|
cannotUndone: "この操作は取り消せません。続行しますか?"
|
||||||
staticLink:
|
staticLink:
|
||||||
title: "Audon リンク"
|
title: "Audon リンク"
|
||||||
hint: "あなたが部屋をホストしたとき、他の人はこの固定 URL からでも参加できます。"
|
hint: "あなたが部屋をホストしたとき、他の人はこの固定 URL からでも参加できます。"
|
||||||
|
@ -82,6 +83,15 @@ speakRequest:
|
||||||
microphoneBlocked: "マイクが禁止されています。ブラウザやデバイスの設定からマイクの使用を許可してください。"
|
microphoneBlocked: "マイクが禁止されています。ブラウザやデバイスの設定からマイクの使用を許可してください。"
|
||||||
closeRoomConfirm: "この部屋を閉じますか?"
|
closeRoomConfirm: "この部屋を閉じますか?"
|
||||||
roomEvent:
|
roomEvent:
|
||||||
closedByHost: "ホストにより部屋が閉じられました。"
|
closedByHost: "部屋が閉じられました。"
|
||||||
removed: "リクエストにより部屋から退去しました。"
|
removed: "リクエストにより部屋から退去しました。"
|
||||||
disconneced: "切断されました。"
|
disconneced: "切断されました。"
|
||||||
|
moderation:
|
||||||
|
promote: "{role} にする"
|
||||||
|
demote: "リスナー に戻す"
|
||||||
|
kick: "追い出す"
|
||||||
|
role:
|
||||||
|
host: "ホスト"
|
||||||
|
cohost: "共同ホスト"
|
||||||
|
speaker: "スピーカー"
|
||||||
|
listener: "リスナー"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { pushNotFound, webfinger } from "../assets/utils";
|
import { pushNotFound, webfinger } from "../assets/utils";
|
||||||
import { useMastodonStore } from "../stores/mastodon";
|
import { useMastodonStore } from "../stores/mastodon";
|
||||||
import { map, some, omit, filter, trim, clone } from "lodash-es";
|
import { map, some, omit, filter, trim, clone, differenceBy } from "lodash-es";
|
||||||
import { darkTheme } from "picmo";
|
import { darkTheme } from "picmo";
|
||||||
import { createPopup } from "@picmo/popup-picker";
|
import { createPopup } from "@picmo/popup-picker";
|
||||||
import { Howl } from "howler";
|
import { Howl } from "howler";
|
||||||
|
@ -126,7 +126,6 @@ export default {
|
||||||
publish: {
|
publish: {
|
||||||
audioBitrate: AudioPresets.music,
|
audioBitrate: AudioPresets.music,
|
||||||
forceStereo: false,
|
forceStereo: false,
|
||||||
source: Track.Source.Microphone,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
roomInfo: {
|
roomInfo: {
|
||||||
|
@ -163,6 +162,7 @@ export default {
|
||||||
showRequestDialog: false,
|
showRequestDialog: false,
|
||||||
showRequestedNotification: false,
|
showRequestedNotification: false,
|
||||||
isEditLoading: false,
|
isEditLoading: false,
|
||||||
|
isRequestLoading: false,
|
||||||
closeLoading: false,
|
closeLoading: false,
|
||||||
showEditDialog: false,
|
showEditDialog: false,
|
||||||
timeElapsed: "",
|
timeElapsed: "",
|
||||||
|
@ -245,7 +245,10 @@ export default {
|
||||||
return this.isSpeaker(myAudonID);
|
return this.isSpeaker(myAudonID);
|
||||||
},
|
},
|
||||||
micStatusIcon() {
|
micStatusIcon() {
|
||||||
if (!this.micGranted) {
|
if (
|
||||||
|
!this.micGranted ||
|
||||||
|
!(this.iamHost || this.iamCohost || this.iamSpeaker)
|
||||||
|
) {
|
||||||
return mdiMicrophoneQuestion;
|
return mdiMicrophoneQuestion;
|
||||||
}
|
}
|
||||||
if (this.iamMuted) {
|
if (this.iamMuted) {
|
||||||
|
@ -385,15 +388,39 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
.on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
||||||
self.roomInfo = JSON.parse(metadata);
|
const newRoominfo = JSON.parse(metadata);
|
||||||
|
const myAudonID = self.donStore.oauth.audon.audon_id;
|
||||||
|
const iamNewCohost = some(
|
||||||
|
differenceBy(
|
||||||
|
newRoominfo.cohosts,
|
||||||
|
self.roomInfo.cohosts,
|
||||||
|
"audon_id"
|
||||||
|
),
|
||||||
|
(v) => v.audon_id === myAudonID
|
||||||
|
);
|
||||||
|
if (iamNewCohost) {
|
||||||
|
self.closeLoading = true;
|
||||||
|
self.roomClient.disconnect();
|
||||||
|
self.donStore.revertAvatar().finally(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const iamNewSpeaker = some(
|
||||||
|
differenceBy(
|
||||||
|
newRoominfo.speakers,
|
||||||
|
self.roomInfo.speakers,
|
||||||
|
"audon_id"
|
||||||
|
),
|
||||||
|
(v) => v.audon_id === myAudonID
|
||||||
|
);
|
||||||
|
self.roomInfo = newRoominfo;
|
||||||
self.editingRoomInfo = clone(self.roomInfo);
|
self.editingRoomInfo = clone(self.roomInfo);
|
||||||
if (!self.roomInfo.speakers) return;
|
|
||||||
for (const speakers of self.roomInfo.speakers) {
|
for (const speakers of self.roomInfo.speakers) {
|
||||||
self.speakRequests.delete(speakers.audon_id);
|
self.speakRequests.delete(speakers.audon_id);
|
||||||
if (self.speakRequests.size < 1)
|
if (self.speakRequests.size < 1)
|
||||||
self.showRequestNotification = false;
|
self.showRequestNotification = false;
|
||||||
}
|
}
|
||||||
if (self.iamSpeaker && !self.micGranted) {
|
if (self.iamSpeaker && iamNewSpeaker) {
|
||||||
self.roomClient.localParticipant
|
self.roomClient.localParticipant
|
||||||
.setMicrophoneEnabled(
|
.setMicrophoneEnabled(
|
||||||
true,
|
true,
|
||||||
|
@ -405,6 +432,7 @@ export default {
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
self.roomClient.localParticipant.setMicrophoneEnabled(false);
|
self.roomClient.localParticipant.setMicrophoneEnabled(false);
|
||||||
|
self.mutedSpeakerIDs.add(myAudonID);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -480,13 +508,19 @@ export default {
|
||||||
this.sounds.request.play();
|
this.sounds.request.play();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onAcceptRequest(identity) {
|
async onModerate(identity, op) {
|
||||||
// promote user to a speaker
|
if (!identity) return;
|
||||||
// the livekit server will update room metadata
|
if (op === "kick" || op === "cohost") {
|
||||||
|
if (!confirm(this.$t("cannotUndone"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isRequestLoading = true;
|
||||||
try {
|
try {
|
||||||
await axios.put(`/api/room/${this.roomID}/${identity}`);
|
await axios.put(`/api/room/${this.roomID}`, { identity, op });
|
||||||
} catch (reqError) {
|
} finally {
|
||||||
console.log("permission update request error: ", reqError);
|
this.isRequestLoading = false;
|
||||||
|
this.speakRequests.delete(identity);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onDeclineRequest(identity) {
|
async onDeclineRequest(identity) {
|
||||||
|
@ -585,6 +619,7 @@ export default {
|
||||||
displayName: account.displayName,
|
displayName: account.displayName,
|
||||||
avatar: account.avatar,
|
avatar: account.avatar,
|
||||||
url: account.url,
|
url: account.url,
|
||||||
|
identity,
|
||||||
};
|
};
|
||||||
if (resp.data.avatar) {
|
if (resp.data.avatar) {
|
||||||
info.avatar = `/storage/${resp.data.audon_id}/avatar/${resp.data.avatar}`;
|
info.avatar = `/storage/${resp.data.audon_id}/avatar/${resp.data.avatar}`;
|
||||||
|
@ -736,7 +771,7 @@ export default {
|
||||||
@connect.once="joinRoom"
|
@connect.once="joinRoom"
|
||||||
></JoinDialog>
|
></JoinDialog>
|
||||||
<v-dialog v-model="showRequestDialog" max-width="500">
|
<v-dialog v-model="showRequestDialog" max-width="500">
|
||||||
<v-card class="d-flex flex-column">
|
<v-card :loading="isRequestLoading" class="d-flex flex-column">
|
||||||
<v-card-title>{{ $t("speakRequest.label") }}</v-card-title>
|
<v-card-title>{{ $t("speakRequest.label") }}</v-card-title>
|
||||||
<v-card-text class="flex-grow-1 overflow-auto py-0">
|
<v-card-text class="flex-grow-1 overflow-auto py-0">
|
||||||
<v-list v-if="speakRequests.size > 0" lines="two" variant="tonal">
|
<v-list v-if="speakRequests.size > 0" lines="two" variant="tonal">
|
||||||
|
@ -758,12 +793,14 @@ export default {
|
||||||
size="small"
|
size="small"
|
||||||
variant="text"
|
variant="text"
|
||||||
:icon="mdiCheck"
|
:icon="mdiCheck"
|
||||||
@click="onAcceptRequest(id)"
|
:disabled="isRequestLoading"
|
||||||
|
@click.once="onModerate(id, 'speaker')"
|
||||||
></v-btn>
|
></v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
size="small"
|
size="small"
|
||||||
variant="text"
|
variant="text"
|
||||||
:icon="mdiClose"
|
:icon="mdiClose"
|
||||||
|
:disabled="isRequestLoading"
|
||||||
@click="onDeclineRequest(id)"
|
@click="onDeclineRequest(id)"
|
||||||
></v-btn>
|
></v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
@ -884,6 +921,8 @@ export default {
|
||||||
:data="cachedMastoData[key]"
|
:data="cachedMastoData[key]"
|
||||||
:muted="mutedSpeakerIDs.has(key)"
|
:muted="mutedSpeakerIDs.has(key)"
|
||||||
:emoji="emojiReactions[key]?.emoji"
|
:emoji="emojiReactions[key]?.emoji"
|
||||||
|
@moderate="onModerate"
|
||||||
|
:enable-menu="iamHost || iamCohost"
|
||||||
>
|
>
|
||||||
</Participant>
|
</Participant>
|
||||||
</template>
|
</template>
|
||||||
|
@ -895,6 +934,8 @@ export default {
|
||||||
:data="cachedMastoData[key]"
|
:data="cachedMastoData[key]"
|
||||||
type="listener"
|
type="listener"
|
||||||
:emoji="emojiReactions[key]?.emoji"
|
:emoji="emojiReactions[key]?.emoji"
|
||||||
|
@moderate="onModerate"
|
||||||
|
:enable-menu="iamHost || iamCohost"
|
||||||
></Participant>
|
></Participant>
|
||||||
</template>
|
</template>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
137
room.go
137
room.go
|
@ -34,7 +34,7 @@ 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
|
// check if user is already hosting or cohosting
|
||||||
lkRooms, err := host.GetCurrentLivekitRooms(c.Request().Context())
|
lkRooms, err := host.GetCurrentLivekitRooms(c.Request().Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
|
@ -46,7 +46,7 @@ func createRoomHandler(c echo.Context) error {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
if meta.Host.Equal(host) {
|
if meta.IsHost(host) || meta.IsCoHost(host) {
|
||||||
return ErrOperationNotPermitted
|
return ErrOperationNotPermitted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func createRoomHandler(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create livekit room
|
// Create livekit room
|
||||||
roomMetadata := &RoomMetadata{Room: room, MastodonAccounts: make(map[string]*MastodonAccount)}
|
roomMetadata := &RoomMetadata{Room: room, Speakers: []*AudonUser{}, Kicked: []*AudonUser{}, MastodonAccounts: make(map[string]*MastodonAccount)}
|
||||||
metadata, _ := json.Marshal(roomMetadata)
|
metadata, _ := json.Marshal(roomMetadata)
|
||||||
_, err = lkRoomServiceClient.CreateRoom(c.Request().Context(), &livekit.CreateRoomRequest{
|
_, err = lkRoomServiceClient.CreateRoom(c.Request().Context(), &livekit.CreateRoomRequest{
|
||||||
Name: room.RoomID,
|
Name: room.RoomID,
|
||||||
|
@ -257,13 +257,14 @@ func joinRoomHandler(c echo.Context) (err error) {
|
||||||
return ErrAlreadyEnded
|
return ErrAlreadyEnded
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 403 if one has been kicked
|
lkRoom, _ := getRoomInLivekit(c.Request().Context(), room.RoomID) // lkRoom will be nil if it doesn't exist
|
||||||
for _, kicked := range room.Kicked {
|
if lkRoom == nil {
|
||||||
if kicked.Equal(user) {
|
return ErrRoomNotFound
|
||||||
return echo.NewHTTPError(http.StatusForbidden)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
roomMetadata, _ := getRoomMetadataFromLivekitRoom(lkRoom)
|
||||||
|
room = roomMetadata.Room
|
||||||
|
|
||||||
canTalk := room.IsHost(user) || room.IsCoHost(user) // host and cohost can talk from the beginning
|
canTalk := room.IsHost(user) || room.IsCoHost(user) // host and cohost can talk from the beginning
|
||||||
|
|
||||||
// check room restriction
|
// check room restriction
|
||||||
|
@ -301,19 +302,18 @@ func joinRoomHandler(c echo.Context) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
roomMetadata := &RoomMetadata{Room: room, MastodonAccounts: make(map[string]*MastodonAccount)}
|
// return 403 if one has been kicked
|
||||||
|
for _, kicked := range roomMetadata.Kicked {
|
||||||
|
if kicked.Equal(user) {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Allows the user to talk if the user is a speaker
|
// Allows the user to talk if the user is a speaker
|
||||||
lkRoom, _ := getRoomInLivekit(c.Request().Context(), room.RoomID) // lkRoom will be nil if it doesn't exist
|
for _, speaker := range roomMetadata.Speakers {
|
||||||
if lkRoom != nil {
|
if speaker.AudonID == user.AudonID {
|
||||||
if existingMetadata, _ := getRoomMetadataFromLivekitRoom(lkRoom); existingMetadata != nil {
|
canTalk = true
|
||||||
roomMetadata = existingMetadata
|
break
|
||||||
for _, speaker := range existingMetadata.Speakers {
|
|
||||||
if speaker.AudonID == user.AudonID {
|
|
||||||
canTalk = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,13 +401,8 @@ func joinRoomHandler(c echo.Context) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update room metadata
|
// Update room metadata
|
||||||
currentMeta, err := getRoomMetadataFromLivekitRoom(lkRoom)
|
roomMetadata.MastodonAccounts[user.AudonID] = mastoAccount
|
||||||
if err != nil {
|
newMetadata, err := json.Marshal(roomMetadata)
|
||||||
c.Logger().Error(err)
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
currentMeta.MastodonAccounts[user.AudonID] = mastoAccount
|
|
||||||
newMetadata, err := json.Marshal(currentMeta)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
|
@ -429,7 +424,7 @@ func joinRoomHandler(c echo.Context) (err error) {
|
||||||
return c.JSON(http.StatusOK, resp)
|
return c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// intended to be called by room's host
|
// intended to be called by room's host or cohost
|
||||||
func closeRoomHandler(c echo.Context) error {
|
func closeRoomHandler(c echo.Context) error {
|
||||||
roomID := c.Param("id")
|
roomID := c.Param("id")
|
||||||
if err := mainValidator.Var(&roomID, "required,printascii"); err != nil {
|
if err := mainValidator.Var(&roomID, "required,printascii"); err != nil {
|
||||||
|
@ -444,15 +439,19 @@ func closeRoomHandler(c echo.Context) error {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// return 410 if the room has already ended
|
// return 410 if the room has already ended
|
||||||
if !room.EndedAt.IsZero() {
|
if !room.EndedAt.IsZero() {
|
||||||
return ErrAlreadyEnded
|
return ErrAlreadyEnded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta := room.getRoomMetadata(c.Request().Context())
|
||||||
|
if meta == nil {
|
||||||
|
return ErrRoomNotFound
|
||||||
|
}
|
||||||
|
|
||||||
// only host or cohost can close the room
|
// only host or cohost can close the room
|
||||||
user := c.Get("user").(*AudonUser)
|
user := c.Get("user").(*AudonUser)
|
||||||
if !room.IsHost(user) && !room.IsCoHost(user) {
|
if !meta.IsHost(user) && !meta.IsCoHost(user) {
|
||||||
return ErrOperationNotPermitted
|
return ErrOperationNotPermitted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,8 +480,8 @@ func leaveRoomHandler(c echo.Context) error {
|
||||||
return c.NoContent(http.StatusOK)
|
return c.NoContent(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePermissionHandler(c echo.Context) error {
|
func updateRoleHandler(c echo.Context) error {
|
||||||
roomID := c.Param("room")
|
roomID := c.Param("id")
|
||||||
|
|
||||||
// look up lkRoom in livekit
|
// look up lkRoom in livekit
|
||||||
lkRoom, exists := getRoomInLivekit(c.Request().Context(), roomID)
|
lkRoom, exists := getRoomInLivekit(c.Request().Context(), roomID)
|
||||||
|
@ -502,7 +501,12 @@ func updatePermissionHandler(c echo.Context) error {
|
||||||
return ErrOperationNotPermitted
|
return ErrOperationNotPermitted
|
||||||
}
|
}
|
||||||
|
|
||||||
audonID := c.Param("user")
|
params := make(map[string]string)
|
||||||
|
if err := c.Bind(¶ms); err != nil {
|
||||||
|
return ErrInvalidRequestFormat
|
||||||
|
}
|
||||||
|
audonID := params["identity"]
|
||||||
|
operation := params["op"]
|
||||||
if !lkRoomMetadata.IsUserInLivekitRoom(c.Request().Context(), audonID) {
|
if !lkRoomMetadata.IsUserInLivekitRoom(c.Request().Context(), audonID) {
|
||||||
return ErrUserNotFound
|
return ErrUserNotFound
|
||||||
}
|
}
|
||||||
|
@ -517,17 +521,48 @@ func updatePermissionHandler(c echo.Context) error {
|
||||||
newPermission := &livekit.ParticipantPermission{
|
newPermission := &livekit.ParticipantPermission{
|
||||||
CanPublishData: true,
|
CanPublishData: true,
|
||||||
CanSubscribe: true,
|
CanSubscribe: true,
|
||||||
|
CanPublish: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// promote user to a speaker
|
if operation == "speaker" {
|
||||||
if c.Request().Method == http.MethodPut {
|
|
||||||
newPermission.CanPublish = true
|
|
||||||
for _, speaker := range lkRoomMetadata.Speakers {
|
for _, speaker := range lkRoomMetadata.Speakers {
|
||||||
if speaker.Equal(tgtUser) {
|
if speaker.Equal(tgtUser) {
|
||||||
return echo.NewHTTPError(http.StatusConflict, "already_speaking")
|
return echo.NewHTTPError(http.StatusConflict, "already_speaking")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lkRoomMetadata.Speakers = append(lkRoomMetadata.Speakers, tgtUser)
|
lkRoomMetadata.Speakers = append(lkRoomMetadata.Speakers, tgtUser)
|
||||||
|
} else if operation == "cohost" {
|
||||||
|
lkRoomMetadata.CoHosts = append(lkRoomMetadata.CoHosts, tgtUser)
|
||||||
|
coll := mainDB.Collection(COLLECTION_ROOM)
|
||||||
|
if _, err = coll.UpdateOne(c.Request().Context(),
|
||||||
|
bson.D{{Key: "room_id", Value: roomID}},
|
||||||
|
bson.D{{Key: "$set", Value: bson.D{{
|
||||||
|
Key: "cohosts",
|
||||||
|
Value: lkRoomMetadata.CoHosts,
|
||||||
|
}}}}); err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
} else if operation == "kick" {
|
||||||
|
lkRoomMetadata.Kicked = append(lkRoomMetadata.Kicked, tgtUser)
|
||||||
|
lkRoomServiceClient.RemoveParticipant(c.Request().Context(), &livekit.RoomParticipantIdentity{
|
||||||
|
Room: roomID,
|
||||||
|
Identity: tgtUser.AudonID,
|
||||||
|
})
|
||||||
|
} else if operation == "demote" {
|
||||||
|
newPermission.CanPublish = false
|
||||||
|
} else {
|
||||||
|
return ErrInvalidRequestFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if operation == "demote" || operation == "cohost" {
|
||||||
|
newSpeakers := make([]*AudonUser, 0, len(lkRoomMetadata.Speakers))
|
||||||
|
for _, v := range lkRoomMetadata.Speakers {
|
||||||
|
if v.AudonID != tgtUser.AudonID {
|
||||||
|
newSpeakers = append(newSpeakers, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lkRoomMetadata.Speakers = newSpeakers
|
||||||
}
|
}
|
||||||
|
|
||||||
newMetadata, err := json.Marshal(lkRoomMetadata)
|
newMetadata, err := json.Marshal(lkRoomMetadata)
|
||||||
|
@ -535,26 +570,28 @@ func updatePermissionHandler(c echo.Context) error {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
_, err = lkRoomServiceClient.UpdateRoomMetadata(c.Request().Context(), &livekit.UpdateRoomMetadataRequest{
|
|
||||||
Room: roomID,
|
if operation != "kick" {
|
||||||
Metadata: string(newMetadata),
|
_, err = lkRoomServiceClient.UpdateParticipant(c.Request().Context(), &livekit.UpdateParticipantRequest{
|
||||||
})
|
Room: roomID,
|
||||||
|
Identity: audonID,
|
||||||
|
Permission: newPermission,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataRequest := &livekit.UpdateRoomMetadataRequest{Room: roomID, Metadata: string(newMetadata)}
|
||||||
|
|
||||||
|
_, err = lkRoomServiceClient.UpdateRoomMetadata(context.Background(), metadataRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := lkRoomServiceClient.UpdateParticipant(c.Request().Context(), &livekit.UpdateParticipantRequest{
|
return c.NoContent(http.StatusOK)
|
||||||
Room: roomID,
|
|
||||||
Identity: audonID,
|
|
||||||
Permission: newPermission,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.Logger().Error(err)
|
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRoomToken(room *Room, user *AudonUser, canTalk bool) (string, error) {
|
func getRoomToken(room *Room, user *AudonUser, canTalk bool) (string, error) {
|
||||||
|
|
19
schema.go
19
schema.go
|
@ -31,6 +31,7 @@ type (
|
||||||
RoomMetadata struct {
|
RoomMetadata struct {
|
||||||
*Room
|
*Room
|
||||||
Speakers []*AudonUser `json:"speakers"`
|
Speakers []*AudonUser `json:"speakers"`
|
||||||
|
Kicked []*AudonUser `json:"kicked"`
|
||||||
MastodonAccounts map[string]*MastodonAccount `json:"accounts"`
|
MastodonAccounts map[string]*MastodonAccount `json:"accounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +42,6 @@ type (
|
||||||
Host *AudonUser `bson:"host" json:"host"`
|
Host *AudonUser `bson:"host" json:"host"`
|
||||||
CoHosts []*AudonUser `bson:"cohosts" json:"cohosts"`
|
CoHosts []*AudonUser `bson:"cohosts" json:"cohosts"`
|
||||||
Restriction JoinRestriction `bson:"restriction" json:"restriction"`
|
Restriction JoinRestriction `bson:"restriction" json:"restriction"`
|
||||||
Kicked []*AudonUser `bson:"kicked" json:"kicked"`
|
|
||||||
EndedAt time.Time `bson:"ended_at" json:"ended_at"`
|
EndedAt time.Time `bson:"ended_at" json:"ended_at"`
|
||||||
CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
CreatedAt time.Time `bson:"created_at" json:"created_at"`
|
||||||
Advertise string `bson:"advertise" json:"advertise"`
|
Advertise string `bson:"advertise" json:"advertise"`
|
||||||
|
@ -90,6 +90,11 @@ func (a *AudonUser) GetCurrentLivekitRooms(ctx context.Context) ([]*livekit.Room
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check host
|
||||||
|
room, _ := getRoomMetadataFromLivekitRoom(r)
|
||||||
|
if room.IsHost(a) {
|
||||||
|
current = append(current, r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return current, nil
|
return current, nil
|
||||||
}
|
}
|
||||||
|
@ -150,6 +155,18 @@ func getRoomMetadataFromLivekitRoom(lkRoom *livekit.Room) (*RoomMetadata, error)
|
||||||
return metadata, nil
|
return metadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Room) getRoomMetadata(ctx context.Context) *RoomMetadata {
|
||||||
|
lkRoom, _ := getRoomInLivekit(ctx, r.RoomID)
|
||||||
|
if lkRoom == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
meta, err := getRoomMetadataFromLivekitRoom(lkRoom)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Room) ExistsInLivekit(ctx context.Context) bool {
|
func (r *Room) ExistsInLivekit(ctx context.Context) bool {
|
||||||
lkRooms, _ := lkRoomServiceClient.ListRooms(ctx, &livekit.ListRoomsRequest{Names: []string{r.RoomID}})
|
lkRooms, _ := lkRoomServiceClient.ListRooms(ctx, &livekit.ListRoomsRequest{Names: []string{r.RoomID}})
|
||||||
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ func main() {
|
||||||
api.POST("/room/:id", joinRoomHandler)
|
api.POST("/room/:id", joinRoomHandler)
|
||||||
api.PATCH("/room/:id", updateRoomHandler)
|
api.PATCH("/room/:id", updateRoomHandler)
|
||||||
api.DELETE("/room/:id", closeRoomHandler)
|
api.DELETE("/room/:id", closeRoomHandler)
|
||||||
api.PUT("/room/:room/:user", updatePermissionHandler)
|
api.PUT("/room/:id", updateRoleHandler)
|
||||||
|
|
||||||
e.Static("/assets", "audon-fe/dist/assets")
|
e.Static("/assets", "audon-fe/dist/assets")
|
||||||
e.Static("/static", "audon-fe/dist/static")
|
e.Static("/static", "audon-fe/dist/static")
|
||||||
|
|
17
user.go
17
user.go
|
@ -82,9 +82,24 @@ func redirectUserHandler(c echo.Context) error {
|
||||||
opts := options.FindOne().SetSort(bson.D{{Key: "created_at", Value: -1}})
|
opts := options.FindOne().SetSort(bson.D{{Key: "created_at", Value: -1}})
|
||||||
var room Room
|
var room Room
|
||||||
|
|
||||||
if err := coll.FindOne(c.Request().Context(), bson.D{{Key: "host.audon_id", Value: user.AudonID}}, opts).Decode(&room); err == nil {
|
if err := coll.FindOne(c.Request().Context(), bson.D{
|
||||||
|
{Key: "host.audon_id", Value: user.AudonID},
|
||||||
|
}, opts).Decode(&room); err == nil {
|
||||||
if room.ExistsInLivekit(c.Request().Context()) {
|
if room.ExistsInLivekit(c.Request().Context()) {
|
||||||
|
// redirect to the hosting room if online
|
||||||
return c.Redirect(http.StatusFound, fmt.Sprintf("/r/%s", room.RoomID))
|
return c.Redirect(http.StatusFound, fmt.Sprintf("/r/%s", room.RoomID))
|
||||||
|
} else {
|
||||||
|
// redirect to the first cohosting room if online
|
||||||
|
status, err := user.GetCurrentRoomStatus(c.Request().Context())
|
||||||
|
if err != nil {
|
||||||
|
c.Logger().Error(err)
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
for _, v := range status {
|
||||||
|
if v.Role == "cohost" {
|
||||||
|
return c.Redirect(http.StatusFound, fmt.Sprintf("/r/%s", v.RoomID))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue