Merge branch 'develop' of github.com:thecodingmachine/workadventure into UpdateShowHideLayer
This commit is contained in:
commit
6f6ad949ca
@ -16,6 +16,7 @@
|
|||||||
- Use `WA.room.getCurrentRoom(): Promise<Room>` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started
|
- Use `WA.room.getCurrentRoom(): Promise<Room>` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started
|
||||||
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu
|
- Use `WA.ui.registerMenuCommand(): void` to add a custom menu
|
||||||
- Use `WA.room.setTiles(): void` to change an array of tiles
|
- Use `WA.room.setTiles(): void` to change an array of tiles
|
||||||
|
- Users blocking now relies on UUID rather than ID. A blocked user that leaves a room and comes back will stay blocked.
|
||||||
|
|
||||||
## Version 1.4.3 - 1.4.4 - 1.4.5
|
## Version 1.4.3 - 1.4.4 - 1.4.5
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export class DebugController {
|
|||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
if (query.token !== ADMIN_API_TOKEN) {
|
||||||
return res.status(401).send("Invalid token sent!");
|
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -308,6 +308,7 @@ export class SocketManager {
|
|||||||
throw new Error("clientUser.userId is not an integer " + thing.id);
|
throw new Error("clientUser.userId is not an integer " + thing.id);
|
||||||
}
|
}
|
||||||
userJoinedZoneMessage.setUserid(thing.id);
|
userJoinedZoneMessage.setUserid(thing.id);
|
||||||
|
userJoinedZoneMessage.setUseruuid(thing.uuid);
|
||||||
userJoinedZoneMessage.setName(thing.name);
|
userJoinedZoneMessage.setName(thing.name);
|
||||||
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
userJoinedZoneMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||||
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
userJoinedZoneMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||||
@ -425,7 +426,6 @@ export class SocketManager {
|
|||||||
// Let's send 2 messages: one to the user joining the group and one to the other user
|
// Let's send 2 messages: one to the user joining the group and one to the other user
|
||||||
const webrtcStartMessage1 = new WebRtcStartMessage();
|
const webrtcStartMessage1 = new WebRtcStartMessage();
|
||||||
webrtcStartMessage1.setUserid(otherUser.id);
|
webrtcStartMessage1.setUserid(otherUser.id);
|
||||||
webrtcStartMessage1.setName(otherUser.name);
|
|
||||||
webrtcStartMessage1.setInitiator(true);
|
webrtcStartMessage1.setInitiator(true);
|
||||||
if (TURN_STATIC_AUTH_SECRET !== "") {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + otherUser.id, TURN_STATIC_AUTH_SECRET);
|
||||||
@ -443,7 +443,6 @@ export class SocketManager {
|
|||||||
|
|
||||||
const webrtcStartMessage2 = new WebRtcStartMessage();
|
const webrtcStartMessage2 = new WebRtcStartMessage();
|
||||||
webrtcStartMessage2.setUserid(user.id);
|
webrtcStartMessage2.setUserid(user.id);
|
||||||
webrtcStartMessage2.setName(user.name);
|
|
||||||
webrtcStartMessage2.setInitiator(false);
|
webrtcStartMessage2.setInitiator(false);
|
||||||
if (TURN_STATIC_AUTH_SECRET !== "") {
|
if (TURN_STATIC_AUTH_SECRET !== "") {
|
||||||
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
const { username, password } = this.getTURNCredentials("" + user.id, TURN_STATIC_AUTH_SECRET);
|
||||||
@ -614,6 +613,7 @@ export class SocketManager {
|
|||||||
if (thing instanceof User) {
|
if (thing instanceof User) {
|
||||||
const userJoinedMessage = new UserJoinedZoneMessage();
|
const userJoinedMessage = new UserJoinedZoneMessage();
|
||||||
userJoinedMessage.setUserid(thing.id);
|
userJoinedMessage.setUserid(thing.id);
|
||||||
|
userJoinedMessage.setUseruuid(thing.uuid);
|
||||||
userJoinedMessage.setName(thing.name);
|
userJoinedMessage.setName(thing.name);
|
||||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(thing.characterLayers));
|
||||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(thing.getPosition()));
|
||||||
|
@ -17,7 +17,7 @@ export enum EventMessage{
|
|||||||
GROUP_CREATE_UPDATE = "group-create-update",
|
GROUP_CREATE_UPDATE = "group-create-update",
|
||||||
GROUP_DELETE = "group-delete",
|
GROUP_DELETE = "group-delete",
|
||||||
SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id.
|
SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id.
|
||||||
ITEM_EVENT = 'item-event',
|
ITEM_EVENT = "item-event",
|
||||||
|
|
||||||
CONNECT_ERROR = "connect_error",
|
CONNECT_ERROR = "connect_error",
|
||||||
CONNECTING_ERROR = "connecting_error",
|
CONNECTING_ERROR = "connecting_error",
|
||||||
@ -47,6 +47,7 @@ export interface MessageUserPositionInterface {
|
|||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
visitCardUrl: string | null;
|
visitCardUrl: string | null;
|
||||||
companion: string | null;
|
companion: string | null;
|
||||||
|
userUuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageUserMovedInterface {
|
export interface MessageUserMovedInterface {
|
||||||
@ -61,57 +62,58 @@ export interface MessageUserJoined {
|
|||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
visitCardUrl: string | null;
|
visitCardUrl: string | null;
|
||||||
companion: string | null;
|
companion: string | null;
|
||||||
|
userUuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PositionInterface {
|
export interface PositionInterface {
|
||||||
x: number,
|
x: number;
|
||||||
y: number
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupCreatedUpdatedMessageInterface {
|
export interface GroupCreatedUpdatedMessageInterface {
|
||||||
position: PositionInterface,
|
position: PositionInterface;
|
||||||
groupId: number,
|
groupId: number;
|
||||||
groupSize: number
|
groupSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebRtcDisconnectMessageInterface {
|
export interface WebRtcDisconnectMessageInterface {
|
||||||
userId: number
|
userId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebRtcSignalReceivedMessageInterface {
|
export interface WebRtcSignalReceivedMessageInterface {
|
||||||
userId: number,
|
userId: number;
|
||||||
signal: SignalData,
|
signal: SignalData;
|
||||||
webRtcUser: string | undefined,
|
webRtcUser: string | undefined;
|
||||||
webRtcPassword: string | undefined
|
webRtcPassword: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewportInterface {
|
export interface ViewportInterface {
|
||||||
left: number,
|
left: number;
|
||||||
top: number,
|
top: number;
|
||||||
right: number,
|
right: number;
|
||||||
bottom: number,
|
bottom: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ItemEventMessageInterface {
|
export interface ItemEventMessageInterface {
|
||||||
itemId: number,
|
itemId: number;
|
||||||
event: string,
|
event: string;
|
||||||
state: unknown,
|
state: unknown;
|
||||||
parameters: unknown
|
parameters: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RoomJoinedMessageInterface {
|
export interface RoomJoinedMessageInterface {
|
||||||
//users: MessageUserPositionInterface[],
|
//users: MessageUserPositionInterface[],
|
||||||
//groups: GroupCreatedUpdatedMessageInterface[],
|
//groups: GroupCreatedUpdatedMessageInterface[],
|
||||||
items: { [itemId: number] : unknown }
|
items: { [itemId: number]: unknown };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlayGlobalMessageInterface {
|
export interface PlayGlobalMessageInterface {
|
||||||
id: string
|
id: string;
|
||||||
type: string
|
type: string;
|
||||||
message: string
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnConnectInterface {
|
export interface OnConnectInterface {
|
||||||
connection: RoomConnection,
|
connection: RoomConnection;
|
||||||
room: RoomJoinedMessageInterface
|
room: RoomJoinedMessageInterface;
|
||||||
}
|
}
|
||||||
|
@ -365,6 +365,7 @@ export class RoomConnection implements RoomConnection {
|
|||||||
visitCardUrl: message.getVisitcardurl(),
|
visitCardUrl: message.getVisitcardurl(),
|
||||||
position: ProtobufClientUtils.toPointInterface(position),
|
position: ProtobufClientUtils.toPointInterface(position),
|
||||||
companion: companion ? companion.getName() : null,
|
companion: companion ? companion.getName() : null,
|
||||||
|
userUuid: message.getUseruuid(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,7 +467,6 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.onMessage(EventMessage.WEBRTC_START, (message: WebRtcStartMessage) => {
|
this.onMessage(EventMessage.WEBRTC_START, (message: WebRtcStartMessage) => {
|
||||||
callback({
|
callback({
|
||||||
userId: message.getUserid(),
|
userId: message.getUserid(),
|
||||||
name: message.getName(),
|
|
||||||
initiator: message.getInitiator(),
|
initiator: message.getInitiator(),
|
||||||
webRtcUser: message.getWebrtcusername() ?? undefined,
|
webRtcUser: message.getWebrtcusername() ?? undefined,
|
||||||
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
||||||
@ -592,9 +592,9 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
this.socket.send(clientToServerMessage.serializeBinary().buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public emitReportPlayerMessage(reportedUserId: number, reportComment: string): void {
|
public emitReportPlayerMessage(reportedUserUuid: string, reportComment: string): void {
|
||||||
const reportPlayerMessage = new ReportPlayerMessage();
|
const reportPlayerMessage = new ReportPlayerMessage();
|
||||||
reportPlayerMessage.setReporteduserid(reportedUserId);
|
reportPlayerMessage.setReporteduseruuid(reportedUserUuid);
|
||||||
reportPlayerMessage.setReportcomment(reportComment);
|
reportPlayerMessage.setReportcomment(reportComment);
|
||||||
|
|
||||||
const clientToServerMessage = new ClientToServerMessage();
|
const clientToServerMessage = new ClientToServerMessage();
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
||||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
import type {PlayerInterface} from "./PlayerInterface";
|
||||||
|
|
||||||
export interface AddPlayerInterface {
|
export interface AddPlayerInterface extends PlayerInterface {
|
||||||
userId: number;
|
|
||||||
name: string;
|
|
||||||
characterLayers: BodyResourceDescriptionInterface[];
|
|
||||||
position: PointInterface;
|
position: PointInterface;
|
||||||
visitCardUrl: string|null;
|
|
||||||
companion: string|null;
|
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ import { soundManager } from "./SoundManager";
|
|||||||
import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore";
|
import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore";
|
||||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
||||||
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
||||||
|
import { playersStore } from "../../Stores/PlayersStore";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface | null;
|
initPosition: PointInterface | null;
|
||||||
@ -597,6 +598,8 @@ export class GameScene extends DirtyScene {
|
|||||||
.then((onConnect: OnConnectInterface) => {
|
.then((onConnect: OnConnectInterface) => {
|
||||||
this.connection = onConnect.connection;
|
this.connection = onConnect.connection;
|
||||||
|
|
||||||
|
playersStore.connectToRoomConnection(this.connection);
|
||||||
|
|
||||||
this.connection.onUserJoins((message: MessageUserJoined) => {
|
this.connection.onUserJoins((message: MessageUserJoined) => {
|
||||||
const userMessage: AddPlayerInterface = {
|
const userMessage: AddPlayerInterface = {
|
||||||
userId: message.userId,
|
userId: message.userId,
|
||||||
@ -605,6 +608,7 @@ export class GameScene extends DirtyScene {
|
|||||||
position: message.position,
|
position: message.position,
|
||||||
visitCardUrl: message.visitCardUrl,
|
visitCardUrl: message.visitCardUrl,
|
||||||
companion: message.companion,
|
companion: message.companion,
|
||||||
|
userUuid: message.userUuid,
|
||||||
};
|
};
|
||||||
this.addPlayer(userMessage);
|
this.addPlayer(userMessage);
|
||||||
});
|
});
|
||||||
|
10
front/src/Phaser/Game/PlayerInterface.ts
Normal file
10
front/src/Phaser/Game/PlayerInterface.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures";
|
||||||
|
|
||||||
|
export interface PlayerInterface {
|
||||||
|
userId: number;
|
||||||
|
name: string;
|
||||||
|
characterLayers: BodyResourceDescriptionInterface[];
|
||||||
|
visitCardUrl: string | null;
|
||||||
|
companion: string | null;
|
||||||
|
userUuid: string;
|
||||||
|
}
|
@ -18,6 +18,7 @@ import { registerMenuCommandStream } from "../../Api/Events/ui/MenuItemRegisterE
|
|||||||
import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem";
|
import { sendMenuClickedEvent } from "../../Api/iframe/Ui/MenuItem";
|
||||||
import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
import { consoleGlobalMessageManagerVisibleStore } from "../../Stores/ConsoleGlobalMessageManagerStore";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
import { playersStore } from "../../Stores/PlayersStore";
|
||||||
|
|
||||||
export const MenuSceneName = "MenuScene";
|
export const MenuSceneName = "MenuScene";
|
||||||
const gameMenuKey = "gameMenu";
|
const gameMenuKey = "gameMenu";
|
||||||
@ -120,7 +121,11 @@ export class MenuScene extends Phaser.Scene {
|
|||||||
showReportScreenStore.subscribe((user) => {
|
showReportScreenStore.subscribe((user) => {
|
||||||
if (user !== null) {
|
if (user !== null) {
|
||||||
this.closeAll();
|
this.closeAll();
|
||||||
this.gameReportElement.open(user.userId, user.userName);
|
const uuid = playersStore.getPlayerById(user.userId)?.userUuid;
|
||||||
|
if (uuid === undefined) {
|
||||||
|
throw new Error("Could not find UUID for user with ID " + user.userId);
|
||||||
|
}
|
||||||
|
this.gameReportElement.open(uuid, user.userName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { MenuScene } from "./MenuScene";
|
import { MenuScene } from "./MenuScene";
|
||||||
import { gameManager } from "../Game/GameManager";
|
import { gameManager } from "../Game/GameManager";
|
||||||
import { blackListManager } from "../../WebRtc/BlackListManager";
|
import { blackListManager } from "../../WebRtc/BlackListManager";
|
||||||
|
import { playersStore } from "../../Stores/PlayersStore";
|
||||||
|
|
||||||
export const gameReportKey = 'gameReport';
|
export const gameReportKey = "gameReport";
|
||||||
export const gameReportRessource = 'resources/html/gameReport.html';
|
export const gameReportRessource = "resources/html/gameReport.html";
|
||||||
|
|
||||||
export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
||||||
private opened: boolean = false;
|
private opened: boolean = false;
|
||||||
|
|
||||||
private userId!: number;
|
private userUuid!: string;
|
||||||
private userName!: string | undefined;
|
private userName!: string | undefined;
|
||||||
private anonymous: boolean;
|
private anonymous: boolean;
|
||||||
|
|
||||||
@ -18,46 +19,46 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
|||||||
this.createFromCache(gameReportKey);
|
this.createFromCache(gameReportKey);
|
||||||
|
|
||||||
if (this.anonymous) {
|
if (this.anonymous) {
|
||||||
const divToHide = this.getChildByID('reportSection') as HTMLElement;
|
const divToHide = this.getChildByID("reportSection") as HTMLElement;
|
||||||
divToHide.hidden = true;
|
divToHide.hidden = true;
|
||||||
const textToHide = this.getChildByID('askActionP') as HTMLElement;
|
const textToHide = this.getChildByID("askActionP") as HTMLElement;
|
||||||
textToHide.hidden = true;
|
textToHide.hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.add.existing(this);
|
scene.add.existing(this);
|
||||||
MenuScene.revealMenusAfterInit(this, gameReportKey);
|
MenuScene.revealMenusAfterInit(this, gameReportKey);
|
||||||
|
|
||||||
this.addListener('click');
|
this.addListener("click");
|
||||||
this.on('click', (event:MouseEvent) => {
|
this.on("click", (event: MouseEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if ((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') {
|
if ((event?.target as HTMLInputElement).id === "gameReportFormSubmit") {
|
||||||
this.submitReport();
|
this.submitReport();
|
||||||
} else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') {
|
} else if ((event?.target as HTMLInputElement).id === "gameReportFormCancel") {
|
||||||
this.close();
|
this.close();
|
||||||
} else if((event?.target as HTMLInputElement).id === 'toggleBlockButton') {
|
} else if ((event?.target as HTMLInputElement).id === "toggleBlockButton") {
|
||||||
this.toggleBlock();
|
this.toggleBlock();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public open(userId: number, userName: string|undefined): void {
|
public open(userUuid: string, userName: string | undefined): void {
|
||||||
if (this.opened) {
|
if (this.opened) {
|
||||||
this.close();
|
this.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userId = userId;
|
this.userUuid = userUuid;
|
||||||
this.userName = userName;
|
this.userName = userName;
|
||||||
|
|
||||||
const mainEl = this.getChildByID('gameReport') as HTMLElement;
|
const mainEl = this.getChildByID("gameReport") as HTMLElement;
|
||||||
this.x = this.getCenteredX(mainEl);
|
this.x = this.getCenteredX(mainEl);
|
||||||
this.y = this.getHiddenY(mainEl);
|
this.y = this.getHiddenY(mainEl);
|
||||||
|
|
||||||
const gameTitleReport = this.getChildByID('nameReported') as HTMLElement;
|
const gameTitleReport = this.getChildByID("nameReported") as HTMLElement;
|
||||||
gameTitleReport.innerText = userName || '';
|
gameTitleReport.innerText = userName || "";
|
||||||
|
|
||||||
const blockButton = this.getChildByID('toggleBlockButton') as HTMLElement;
|
const blockButton = this.getChildByID("toggleBlockButton") as HTMLElement;
|
||||||
blockButton.innerText = blackListManager.isBlackListed(this.userId) ? 'Unblock this user' : 'Block this user';
|
blockButton.innerText = blackListManager.isBlackListed(this.userUuid) ? "Unblock this user" : "Block this user";
|
||||||
|
|
||||||
this.opened = true;
|
this.opened = true;
|
||||||
|
|
||||||
@ -67,19 +68,19 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
|||||||
targets: this,
|
targets: this,
|
||||||
y: this.getCenteredY(mainEl),
|
y: this.getCenteredY(mainEl),
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
ease: 'Power3'
|
ease: "Power3",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public close(): void {
|
public close(): void {
|
||||||
gameManager.getCurrentGameScene(this.scene).userInputManager.restoreControls();
|
gameManager.getCurrentGameScene(this.scene).userInputManager.restoreControls();
|
||||||
this.opened = false;
|
this.opened = false;
|
||||||
const mainEl = this.getChildByID('gameReport') as HTMLElement;
|
const mainEl = this.getChildByID("gameReport") as HTMLElement;
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: this,
|
targets: this,
|
||||||
y: this.getHiddenY(mainEl),
|
y: this.getHiddenY(mainEl),
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
ease: 'Power3'
|
ease: "Power3",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,24 +96,25 @@ export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private toggleBlock(): void {
|
private toggleBlock(): void {
|
||||||
!blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId);
|
!blackListManager.isBlackListed(this.userUuid)
|
||||||
|
? blackListManager.blackList(this.userUuid)
|
||||||
|
: blackListManager.cancelBlackList(this.userUuid);
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private submitReport(): void {
|
private submitReport(): void {
|
||||||
const gamePError = this.getChildByID('gameReportErr') as HTMLParagraphElement;
|
const gamePError = this.getChildByID("gameReportErr") as HTMLParagraphElement;
|
||||||
gamePError.innerText = '';
|
gamePError.innerText = "";
|
||||||
gamePError.style.display = 'none';
|
gamePError.style.display = "none";
|
||||||
const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement;
|
const gameTextArea = this.getChildByID("gameReportInput") as HTMLInputElement;
|
||||||
if (!gameTextArea || !gameTextArea.value) {
|
if (!gameTextArea || !gameTextArea.value) {
|
||||||
gamePError.innerText = 'Report message cannot to be empty.';
|
gamePError.innerText = "Report message cannot to be empty.";
|
||||||
gamePError.style.display = 'block';
|
gamePError.style.display = "block";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gameManager.getCurrentGameScene(this.scene).connection?.emitReportPlayerMessage(
|
gameManager
|
||||||
this.userId,
|
.getCurrentGameScene(this.scene)
|
||||||
gameTextArea.value
|
.connection?.emitReportPlayerMessage(this.userUuid, gameTextArea.value);
|
||||||
);
|
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
front/src/Stores/PlayersStore.ts
Normal file
44
front/src/Stores/PlayersStore.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
import type { PlayerInterface } from "../Phaser/Game/PlayerInterface";
|
||||||
|
import type { RoomConnection } from "../Connexion/RoomConnection";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains the list of players currently known.
|
||||||
|
*/
|
||||||
|
function createPlayersStore() {
|
||||||
|
let players = new Map<number, PlayerInterface>();
|
||||||
|
|
||||||
|
const { subscribe, set, update } = writable(players);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
connectToRoomConnection: (roomConnection: RoomConnection) => {
|
||||||
|
players = new Map<number, PlayerInterface>();
|
||||||
|
set(players);
|
||||||
|
roomConnection.onUserJoins((message) => {
|
||||||
|
update((users) => {
|
||||||
|
users.set(message.userId, {
|
||||||
|
userId: message.userId,
|
||||||
|
name: message.name,
|
||||||
|
characterLayers: message.characterLayers,
|
||||||
|
visitCardUrl: message.visitCardUrl,
|
||||||
|
companion: message.companion,
|
||||||
|
userUuid: message.userUuid,
|
||||||
|
});
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
roomConnection.onUserLeft((userId) => {
|
||||||
|
update((users) => {
|
||||||
|
users.delete(userId);
|
||||||
|
return users;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getPlayerById(userId: number): PlayerInterface | undefined {
|
||||||
|
return players.get(userId);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const playersStore = createPlayersStore();
|
@ -1,23 +1,26 @@
|
|||||||
import {Subject} from 'rxjs';
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
class BlackListManager {
|
class BlackListManager {
|
||||||
private list: number[] = [];
|
private list: string[] = [];
|
||||||
public onBlockStream: Subject<number> = new Subject();
|
public onBlockStream: Subject<string> = new Subject();
|
||||||
public onUnBlockStream: Subject<number> = new Subject();
|
public onUnBlockStream: Subject<string> = new Subject();
|
||||||
|
|
||||||
isBlackListed(userId: number): boolean {
|
isBlackListed(userUuid: string): boolean {
|
||||||
return this.list.find((data) => data === userId) !== undefined;
|
return this.list.find((data) => data === userUuid) !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
blackList(userId: number): void {
|
blackList(userUuid: string): void {
|
||||||
if (this.isBlackListed(userId)) return;
|
if (this.isBlackListed(userUuid)) return;
|
||||||
this.list.push(userId);
|
this.list.push(userUuid);
|
||||||
this.onBlockStream.next(userId);
|
this.onBlockStream.next(userUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelBlackList(userId: number): void {
|
cancelBlackList(userUuid: string): void {
|
||||||
this.list.splice(this.list.findIndex(data => data === userId), 1);
|
this.list.splice(
|
||||||
this.onUnBlockStream.next(userId);
|
this.list.findIndex((data) => data === userUuid),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
this.onUnBlockStream.next(userUuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ import { get } from "svelte/store";
|
|||||||
import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
||||||
import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore";
|
import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore";
|
||||||
import { discussionManager } from "./DiscussionManager";
|
import { discussionManager } from "./DiscussionManager";
|
||||||
|
import { playersStore } from "../Stores/PlayersStore";
|
||||||
|
|
||||||
export interface UserSimplePeerInterface {
|
export interface UserSimplePeerInterface {
|
||||||
userId: number;
|
userId: number;
|
||||||
name?: string;
|
|
||||||
initiator?: boolean;
|
initiator?: boolean;
|
||||||
webRtcUser?: string | undefined;
|
webRtcUser?: string | undefined;
|
||||||
webRtcPassword?: string | undefined;
|
webRtcPassword?: string | undefined;
|
||||||
@ -153,10 +153,7 @@ export class SimplePeer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = user.name;
|
const name = this.getName(user.userId);
|
||||||
if (!name) {
|
|
||||||
name = this.getName(user.userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
discussionManager.removeParticipant(user.userId);
|
discussionManager.removeParticipant(user.userId);
|
||||||
|
|
||||||
@ -191,7 +188,7 @@ export class SimplePeer {
|
|||||||
|
|
||||||
//Create a notification for first user in circle discussion
|
//Create a notification for first user in circle discussion
|
||||||
if (this.PeerConnectionArray.size === 0) {
|
if (this.PeerConnectionArray.size === 0) {
|
||||||
mediaManager.createNotification(user.name ?? "");
|
mediaManager.createNotification(name);
|
||||||
}
|
}
|
||||||
this.PeerConnectionArray.set(user.userId, peer);
|
this.PeerConnectionArray.set(user.userId, peer);
|
||||||
|
|
||||||
@ -202,12 +199,7 @@ export class SimplePeer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getName(userId: number): string {
|
private getName(userId: number): string {
|
||||||
const userSearch = this.Users.find((userSearch: UserSimplePeerInterface) => userSearch.userId === userId);
|
return playersStore.getPlayerById(userId)?.name || "";
|
||||||
if (userSearch) {
|
|
||||||
return userSearch.name || "";
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -372,7 +364,8 @@ export class SimplePeer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
|
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
|
||||||
if (blackListManager.isBlackListed(data.userId)) return;
|
const uuid = playersStore.getPlayerById(data.userId)?.userUuid || "";
|
||||||
|
if (blackListManager.isBlackListed(uuid)) return;
|
||||||
console.log("receiveWebrtcScreenSharingSignal", data);
|
console.log("receiveWebrtcScreenSharingSignal", data);
|
||||||
const streamResult = get(screenSharingLocalStreamStore);
|
const streamResult = get(screenSharingLocalStreamStore);
|
||||||
let stream: MediaStream | null = null;
|
let stream: MediaStream | null = null;
|
||||||
@ -473,7 +466,8 @@ export class SimplePeer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sendLocalScreenSharingStreamToUser(userId: number, localScreenCapture: MediaStream): void {
|
private sendLocalScreenSharingStreamToUser(userId: number, localScreenCapture: MediaStream): void {
|
||||||
if (blackListManager.isBlackListed(userId)) return;
|
const uuid = playersStore.getPlayerById(userId)?.userUuid || "";
|
||||||
|
if (blackListManager.isBlackListed(uuid)) return;
|
||||||
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
|
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
|
||||||
if (this.PeerScreenSharingConnectionArray.has(userId)) {
|
if (this.PeerScreenSharingConnectionArray.has(userId)) {
|
||||||
this.pushScreenSharingToRemoteUser(userId, localScreenCapture);
|
this.pushScreenSharingToRemoteUser(userId, localScreenCapture);
|
||||||
|
@ -8,6 +8,7 @@ import type { UserSimplePeerInterface } from "./SimplePeer";
|
|||||||
import { get, readable, Readable } from "svelte/store";
|
import { get, readable, Readable } from "svelte/store";
|
||||||
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
import { obtainedMediaConstraintStore } from "../Stores/MediaStore";
|
||||||
import { discussionManager } from "./DiscussionManager";
|
import { discussionManager } from "./DiscussionManager";
|
||||||
|
import { playersStore } from "../Stores/PlayersStore";
|
||||||
|
|
||||||
const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer");
|
const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer");
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ export class VideoPeer extends Peer {
|
|||||||
private remoteStream!: MediaStream;
|
private remoteStream!: MediaStream;
|
||||||
private blocked: boolean = false;
|
private blocked: boolean = false;
|
||||||
public readonly userId: number;
|
public readonly userId: number;
|
||||||
|
public readonly userUuid: string;
|
||||||
public readonly uniqueId: string;
|
public readonly uniqueId: string;
|
||||||
private onBlockSubscribe: Subscription;
|
private onBlockSubscribe: Subscription;
|
||||||
private onUnBlockSubscribe: Subscription;
|
private onUnBlockSubscribe: Subscription;
|
||||||
@ -60,6 +62,7 @@ export class VideoPeer extends Peer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.userId = user.userId;
|
this.userId = user.userId;
|
||||||
|
this.userUuid = playersStore.getPlayerById(this.userId)?.userUuid || "";
|
||||||
this.uniqueId = "video_" + this.userId;
|
this.uniqueId = "video_" + this.userId;
|
||||||
|
|
||||||
this.streamStore = readable<MediaStream | null>(null, (set) => {
|
this.streamStore = readable<MediaStream | null>(null, (set) => {
|
||||||
@ -181,20 +184,20 @@ export class VideoPeer extends Peer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.pushVideoToRemoteUser(localStream);
|
this.pushVideoToRemoteUser(localStream);
|
||||||
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => {
|
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userUuid) => {
|
||||||
if (userId === this.userId) {
|
if (userUuid === this.userUuid) {
|
||||||
this.toggleRemoteStream(false);
|
this.toggleRemoteStream(false);
|
||||||
this.sendBlockMessage(true);
|
this.sendBlockMessage(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userId) => {
|
this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userUuid) => {
|
||||||
if (userId === this.userId) {
|
if (userUuid === this.userUuid) {
|
||||||
this.toggleRemoteStream(true);
|
this.toggleRemoteStream(true);
|
||||||
this.sendBlockMessage(false);
|
this.sendBlockMessage(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (blackListManager.isBlackListed(this.userId)) {
|
if (blackListManager.isBlackListed(this.userUuid)) {
|
||||||
this.sendBlockMessage(true);
|
this.sendBlockMessage(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +234,7 @@ export class VideoPeer extends Peer {
|
|||||||
private stream(stream: MediaStream) {
|
private stream(stream: MediaStream) {
|
||||||
try {
|
try {
|
||||||
this.remoteStream = stream;
|
this.remoteStream = stream;
|
||||||
if (blackListManager.isBlackListed(this.userId) || this.blocked) {
|
if (blackListManager.isBlackListed(this.userUuid) || this.blocked) {
|
||||||
this.toggleRemoteStream(false);
|
this.toggleRemoteStream(false);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -62,7 +62,7 @@ message WebRtcSignalToServerMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message ReportPlayerMessage {
|
message ReportPlayerMessage {
|
||||||
int32 reportedUserId = 1;
|
string reportedUserUuid = 1;
|
||||||
string reportComment = 2;
|
string reportComment = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +158,7 @@ message UserJoinedMessage {
|
|||||||
PositionMessage position = 4;
|
PositionMessage position = 4;
|
||||||
CompanionMessage companion = 5;
|
CompanionMessage companion = 5;
|
||||||
string visitCardUrl = 6;
|
string visitCardUrl = 6;
|
||||||
|
string userUuid = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserLeftMessage {
|
message UserLeftMessage {
|
||||||
@ -183,7 +184,6 @@ message RoomJoinedMessage {
|
|||||||
|
|
||||||
message WebRtcStartMessage {
|
message WebRtcStartMessage {
|
||||||
int32 userId = 1;
|
int32 userId = 1;
|
||||||
string name = 2;
|
|
||||||
bool initiator = 3;
|
bool initiator = 3;
|
||||||
string webrtcUserName = 4;
|
string webrtcUserName = 4;
|
||||||
string webrtcPassword = 5;
|
string webrtcPassword = 5;
|
||||||
@ -286,6 +286,7 @@ message UserJoinedZoneMessage {
|
|||||||
Zone fromZone = 5;
|
Zone fromZone = 5;
|
||||||
CompanionMessage companion = 6;
|
CompanionMessage companion = 6;
|
||||||
string visitCardUrl = 7;
|
string visitCardUrl = 7;
|
||||||
|
string userUuid = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserLeftZoneMessage {
|
message UserLeftZoneMessage {
|
||||||
|
@ -16,7 +16,7 @@ export class DebugController {
|
|||||||
const query = parse(req.getQuery());
|
const query = parse(req.getQuery());
|
||||||
|
|
||||||
if (query.token !== ADMIN_API_TOKEN) {
|
if (query.token !== ADMIN_API_TOKEN) {
|
||||||
return res.status(401).send("Invalid token sent!");
|
return res.writeStatus("401 Unauthorized").end("Invalid token sent!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -39,6 +39,7 @@ export type LeavesCallback = (thing: Movable, listener: User) => void;*/
|
|||||||
export class UserDescriptor {
|
export class UserDescriptor {
|
||||||
private constructor(
|
private constructor(
|
||||||
public readonly userId: number,
|
public readonly userId: number,
|
||||||
|
private userUuid: string,
|
||||||
private name: string,
|
private name: string,
|
||||||
private characterLayers: CharacterLayerMessage[],
|
private characterLayers: CharacterLayerMessage[],
|
||||||
private position: PositionMessage,
|
private position: PositionMessage,
|
||||||
@ -57,6 +58,7 @@ export class UserDescriptor {
|
|||||||
}
|
}
|
||||||
return new UserDescriptor(
|
return new UserDescriptor(
|
||||||
message.getUserid(),
|
message.getUserid(),
|
||||||
|
message.getUseruuid(),
|
||||||
message.getName(),
|
message.getName(),
|
||||||
message.getCharacterlayersList(),
|
message.getCharacterlayersList(),
|
||||||
position,
|
position,
|
||||||
@ -84,6 +86,7 @@ export class UserDescriptor {
|
|||||||
userJoinedMessage.setVisitcardurl(this.visitCardUrl);
|
userJoinedMessage.setVisitcardurl(this.visitCardUrl);
|
||||||
}
|
}
|
||||||
userJoinedMessage.setCompanion(this.companion);
|
userJoinedMessage.setCompanion(this.companion);
|
||||||
|
userJoinedMessage.setUseruuid(this.userUuid);
|
||||||
|
|
||||||
return userJoinedMessage;
|
return userJoinedMessage;
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,6 @@ export interface AdminSocketData {
|
|||||||
|
|
||||||
export class SocketManager implements ZoneEventListener {
|
export class SocketManager implements ZoneEventListener {
|
||||||
private rooms: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
private rooms: Map<string, PusherRoom> = new Map<string, PusherRoom>();
|
||||||
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
|
||||||
@ -191,8 +190,6 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
.on("data", (message: ServerToClientMessage) => {
|
.on("data", (message: ServerToClientMessage) => {
|
||||||
if (message.hasRoomjoinedmessage()) {
|
if (message.hasRoomjoinedmessage()) {
|
||||||
client.userId = (message.getRoomjoinedmessage() as RoomJoinedMessage).getCurrentuserid();
|
client.userId = (message.getRoomjoinedmessage() as RoomJoinedMessage).getCurrentuserid();
|
||||||
// TODO: do we need this.sockets anymore?
|
|
||||||
this.sockets.set(client.userId, client);
|
|
||||||
|
|
||||||
// If this is the first message sent, send back the viewport.
|
// If this is the first message sent, send back the viewport.
|
||||||
this.handleViewport(client, viewport);
|
this.handleViewport(client, viewport);
|
||||||
@ -302,14 +299,8 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
|
|
||||||
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
async handleReportMessage(client: ExSocketInterface, reportPlayerMessage: ReportPlayerMessage) {
|
||||||
try {
|
try {
|
||||||
const reportedSocket = this.sockets.get(reportPlayerMessage.getReporteduserid());
|
|
||||||
if (!reportedSocket) {
|
|
||||||
throw "reported socket user not found";
|
|
||||||
}
|
|
||||||
//TODO report user on admin application
|
|
||||||
//todo: move to back because this fail if the reported player is in another pusher.
|
|
||||||
await adminApi.reportPlayer(
|
await adminApi.reportPlayer(
|
||||||
reportedSocket.userUuid,
|
reportPlayerMessage.getReporteduseruuid(),
|
||||||
reportPlayerMessage.getReportcomment(),
|
reportPlayerMessage.getReportcomment(),
|
||||||
client.userUuid,
|
client.userUuid,
|
||||||
client.roomId.split("/")[2]
|
client.roomId.split("/")[2]
|
||||||
@ -334,14 +325,6 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
socket.backConnection.write(pusherToBackMessage);
|
socket.backConnection.write(pusherToBackMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private searchClientByIdOrFail(userId: number): ExSocketInterface {
|
|
||||||
const client: ExSocketInterface | undefined = this.sockets.get(userId);
|
|
||||||
if (client === undefined) {
|
|
||||||
throw new Error("Could not find user with id " + userId);
|
|
||||||
}
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
leaveRoom(socket: ExSocketInterface) {
|
leaveRoom(socket: ExSocketInterface) {
|
||||||
// leave previous room and world
|
// leave previous room and world
|
||||||
try {
|
try {
|
||||||
@ -364,9 +347,8 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
//Client.leave(Client.roomId);
|
//Client.leave(Client.roomId);
|
||||||
} finally {
|
} finally {
|
||||||
//delete Client.roomId;
|
//delete Client.roomId;
|
||||||
this.sockets.delete(socket.userId);
|
|
||||||
clientEventsEmitter.emitClientLeave(socket.userUuid, socket.roomId);
|
clientEventsEmitter.emitClientLeave(socket.userUuid, socket.roomId);
|
||||||
console.log("A user left (", this.sockets.size, " connected users)");
|
console.log("A user left");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -410,15 +392,6 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
return this.rooms;
|
return this.rooms;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchClientByUuid(uuid: string): ExSocketInterface | null {
|
|
||||||
for (const socket of this.sockets.values()) {
|
|
||||||
if (socket.userUuid === uuid) {
|
|
||||||
return socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
public handleQueryJitsiJwtMessage(client: ExSocketInterface, queryJitsiJwtMessage: QueryJitsiJwtMessage) {
|
||||||
try {
|
try {
|
||||||
const room = queryJitsiJwtMessage.getJitsiroom();
|
const room = queryJitsiJwtMessage.getJitsiroom();
|
||||||
|
Loading…
Reference in New Issue
Block a user