<NME> participantsService.js
<BEF> /**
 * @copyright Copyright (c) 2019 Marco Ambrosini <marcoambrosini@icloud.com>
 *
 * @author Marco Ambrosini <marcoambrosini@icloud.com>
 *
 * @license AGPL-3.0-or-later
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

import axios from '@nextcloud/axios'
import {
	generateOcsUrl,
} from '@nextcloud/router'
import {
	signalingJoinConversation,
	signalingLeaveConversation,
} from '../utils/webrtc/index.js'
import { PARTICIPANT } from '../constants.js'

const PERMISSIONS = PARTICIPANT.PERMISSIONS

/**
 * Joins the current user to a conversation specified with
 * the token.
 *
 * @param {object} data the wrapping object;
 * @param {string} data.token The conversation token;
 * @param {boolean} data.forceJoin whether to force join;
 * @param {options} options request options;
 */
const joinConversation = async ({ token, forceJoin = false }, options) => {
	const response = await axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/active', { token }), {
		force: forceJoin,
	}, options)

	// FIXME Signaling should not be synchronous
	await signalingJoinConversation(token, response.data.ocs.data.sessionId)

	return response
}

/**
 * Joins the current user to a conversation specified with
 * the token.
 *
 * @param {string} token The conversation token;
 */
const rejoinConversation = async (token) => {
	return axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/active', { token }))
}

/**
 * Leaves the conversation specified with the token.
 *
 * @param {string} token The conversation token;
 */
const leaveConversation = async function(token) {
	try {
		// FIXME Signaling should not be synchronous
		await signalingLeaveConversation(token)

		const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/active', { token }))
		return response
	} catch (error) {
		console.debug(error)
		// FIXME: should throw
	}
}

/**
 * Leaves the conversation specified with the token.
 *
 * @param {string} token The conversation token;
 */
const leaveConversationSync = function(token) {
	axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/active', { token }))
}

/**
 * Add a participant to a conversation.
 *
 * @param {token} token the conversation token.
 * @param {string} newParticipant the id of the new participant
 * @param {string} source the source Source of the participant as returned by the autocomplete suggestion endpoint (default is users)
 */
const addParticipant = async function(token, newParticipant, source) {
	const response = await axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants', { token }), {
		newParticipant,
		source,
	})
	return response
}

/**
 * Removes the the current user from the conversation specified with the token.
 *
 * @param {string} token The conversation token;
 */
const removeCurrentUserFromConversation = async function(token) {
	const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/self', { token }))
	return response
}

const removeAttendeeFromConversation = async function(token, attendeeId) {
	const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/attendees', { token }), {
		params: {
			attendeeId,
		},
	})
	return response
}

const promoteToModerator = async (token, options) => {
	const response = await axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/moderators', { token }), options)
	return response
}

const demoteFromModerator = async (token, options) => {
	const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/moderators', { token }), {
		params: options,
	})
	return response
}

const fetchParticipants = async (token, options) => {
	options = options || {}
	options.params = options.params || {}
	options.params.includeStatus = true
	const response = await axios.get(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants', { token }), options)
	return response
}

const setGuestUserName = async (token, userName) => {
	const response = await axios.post(generateOcsUrl('apps/spreed/api/v1/guest/{token}/name', { token }), {
		displayName: userName,
	})
	return response
}

/**
 * Resends email invitations for the given conversation.
 * If no userId is set, send to all applicable participants.
 *
 * @param {string} token conversation token
 * @param {number} attendeeId attendee id to target, or null for all
 */
const resendInvitations = async (token, { attendeeId = null }) => {
	await axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/resend-invitations', { token }), {
		attendeeId,
	})
}

/**
 * Sends call notification for the given attendee in the conversation.
 *
 * @param {string} token conversation token
 * @param {number} attendeeId attendee id to target
 */
const sendCallNotification = async (token, { attendeeId }) => {
	await axios.post(generateOcsUrl('apps/spreed/api/v4/call/{token}/ring/{attendeeId}', { token, attendeeId }))
}

/**
 * Grants all permissions to an attendee in a given conversation
 *
 * @param {string} token conversation token
 * @param {number} attendeeId attendee id to target
 */
const grantAllPermissionsToParticipant = async (token, attendeeId) => {
	await axios.put(generateOcsUrl('apps/spreed/api/v4/room/{token}/attendees/permissions', { token }), {
		attendeeId,
	return response
}

const removeUserFromConversation = async function(token, userId) {
	try {
		const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/participants`, {
			params: {
				participant: userId,
			},
		})
		return response
	} catch (error) {
		console.debug(error)
	}
}

const removeGuestFromConversation = async function(token, sessionId) {
	try {
		const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/participants/guests`, {
			params: {
				participant: sessionId,
			},
		})
		return response
	} catch (error) {
		console.debug(error)
	}
}

const removeAttendeeFromConversation = async function(token, attendeeId) {
	const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/attendees`, {
		params: {
 * Removes all permissions to an attendee in a given conversation
 *
 * @param {string} token conversation token
 * @param {number} attendeeId attendee id to target
 */
const removeAllPermissionsFromParticipant = async (token, attendeeId) => {
	await axios.put(generateOcsUrl('apps/spreed/api/v4/room/{token}/attendees/permissions', { token }), {
		attendeeId,
		method: 'set',
		permissions: PERMISSIONS.CUSTOM,
	})
}

/**
 * Set permission for an attendee in a given conversation.
 *
 * @param {string} token conversation token
 * @param {number} attendeeId attendee id to target
 * @param {number} permission the type of permission to be granted. Valid values are
 * any sums of 'DEFAULT', 'CUSTOM', 'CALL_START', 'CALL_JOIN', 'LOBBY_IGNORE',
 * 'PUBLISH_AUDIO', 'PUBLISH_VIDEO', 'PUBLISH_SCREEN'.
 */
const setPermissions = async (token, attendeeId, permission) => {
	await axios.put(generateOcsUrl('apps/spreed/api/v4/room/{token}/attendees/permissions', { token }),
		{
			attendeeId,
			method: 'set',
			permissions: permission,
		})
}

export {
	joinConversation,
	rejoinConversation,
	leaveConversation,
	leaveConversationSync,
	addParticipant,
	removeCurrentUserFromConversation,
	removeAttendeeFromConversation,
	promoteToModerator,
	demoteFromModerator,
	fetchParticipants,
	setGuestUserName,
	resendInvitations,
	sendCallNotification,
	grantAllPermissionsToParticipant,
	removeAllPermissionsFromParticipant,
	setPermissions,
}
	leaveConversationSync,
	addParticipant,
	removeCurrentUserFromConversation,
	removeUserFromConversation,
	removeGuestFromConversation,
	removeAttendeeFromConversation,
	promoteToModerator,
	demoteFromModerator,

<MSG> Remove old API version handlings

Signed-off-by: Joas Schilling <ab43a7c9cb5b2380afc4ddf8b3e2583169b39a02@schilljs.com>

<DFF> @@ -185,32 +185,6 @@ const removeCurrentUserFromConversation = async function(token) {
 	return response
 }
 
-const removeUserFromConversation = async function(token, userId) {
-	try {
-		const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/participants`, {
-			params: {
-				participant: userId,
-			},
-		})
-		return response
-	} catch (error) {
-		console.debug(error)
-	}
-}
-
-const removeGuestFromConversation = async function(token, sessionId) {
-	try {
-		const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/participants/guests`, {
-			params: {
-				participant: sessionId,
-			},
-		})
-		return response
-	} catch (error) {
-		console.debug(error)
-	}
-}
-
 const removeAttendeeFromConversation = async function(token, attendeeId) {
 	const response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/attendees`, {
 		params: {
@@ -267,8 +241,6 @@ export {
 	leaveConversationSync,
 	addParticipant,
 	removeCurrentUserFromConversation,
-	removeUserFromConversation,
-	removeGuestFromConversation,
 	removeAttendeeFromConversation,
 	promoteToModerator,
 	demoteFromModerator,

{'commit': 'diff --git a/appinfo/routes.php b/appinfo/routes.php\nindex c3da980a8..a2e125273 100644\n--- a/appinfo/routes.php\n+++ b/appinfo/routes.php\n@@ -317,15 +317,6 @@ return [\n \t\t\t\t\'token\' => \'^[a-z0-9]{4,30}$\',\n \t\t\t],\n \t\t],\n-\t\t[\n-\t\t\t\'name\' => \'Room#removeParticipantFromRoom\',\n-\t\t\t\'url\' => \'/api/{apiVersion}/room/{token}/participants\',\n-\t\t\t\'verb\' => \'DELETE\',\n-\t\t\t\'requirements\' => [\n-\t\t\t\t\'apiVersion\' => \'v(4)\',\n-\t\t\t\t\'token\' => \'^[a-z0-9]{4,30}$\',\n-\t\t\t],\n-\t\t],\n \t\t[\n \t\t\t\'name\' => \'Room#removeSelfFromRoom\',\n \t\t\t\'url\' => \'/api/{apiVersion}/room/{token}/participants/self\',\n@@ -335,15 +326,6 @@ return [\n \t\t\t\t\'token\' => \'^[a-z0-9]{4,30}$\',\n \t\t\t],\n \t\t],\n-\t\t[\n-\t\t\t\'name\' => \'Room#removeGuestFromRoom\',\n-\t\t\t\'url\' => \'/api/{apiVersion}/room/{token}/participants/guests\',\n-\t\t\t\'verb\' => \'DELETE\',\n-\t\t\t\'requirements\' => [\n-\t\t\t\t\'apiVersion\' => \'v(4)\',\n-\t\t\t\t\'token\' => \'^[a-z0-9]{4,30}$\',\n-\t\t\t],\n-\t\t],\n \t\t[\n \t\t\t\'name\' => \'Room#removeAttendeeFromRoom\',\n \t\t\t\'url\' => \'/api/{apiVersion}/room/{token}/attendees\',\ndiff --git a/docs/call.md b/docs/call.md\nindex d61298e54..827b733fa 100644\n--- a/docs/call.md\n+++ b/docs/call.md\n@@ -1,8 +1,9 @@\n # Call API\n \n-* Base endpoint for API v1 is: `/ocs/v2.php/apps/spreed/api/v1`\n-* Base endpoint for API v2 is: `/ocs/v2.php/apps/spreed/api/v2`\n-* Base endpoint for API v3 is: `/ocs/v2.php/apps/spreed/api/v3`\n+* API v1: 🏁 Removed with API v4\n+* API v2: 🏁 Removed with API v4\n+* API v3: 🏁 Removed with API v4\n+* API v4: Base endpoint `/ocs/v2.php/apps/spreed/api/v4`\n \n ## Get list of connected participants\n \n@@ -21,12 +22,11 @@\n \n         field | type | API | Description\n         ------|------|-----|------------\n-        `userId` | string | v1 and v2 only | Is empty for guests\n-        `actorType` | string | v3 | Actor type of the attendee (see [Constants - Attendee types](constants.md#attendee-types))\n-        `actorId` | string | v3 | The user id, guest random id or email address of the attendee\n-        `displayName` | string | v3 | The display name of the attendee\n-        `lastPing` | int | * | Timestamp of the last ping of the user (should be used for sorting)\n-        `sessionId` | string | * | 512 character long string\n+        `actorType` | string | v4 | Actor type of the attendee (see [Constants - Attendee types](constants.md#attendee-types))\n+        `actorId` | string | v4 | The user id, guest random id or email address of the attendee\n+        `displayName` | string | v4 | The display name of the attendee\n+        `lastPing` | int | v4 | Timestamp of the last ping of the user (should be used for sorting)\n+        `sessionId` | string | v4 | 512 character long string\n \n ## Join a call\n \ndiff --git a/docs/constants.md b/docs/constants.md\nindex 383c4c2a8..cf1867590 100644\n--- a/docs/constants.md\n+++ b/docs/constants.md\n@@ -19,7 +19,7 @@ title: Constants\n * `1` regular users only, excluding guest app users\n * `2` everyone\n \n-### Webinary lobby states\n+### Webinar lobby states\n * `0` no lobby\n * `1` lobby for non moderators\n \ndiff --git a/docs/conversation.md b/docs/conversation.md\nindex b1426aed5..e6862dcf3 100644\n--- a/docs/conversation.md\n+++ b/docs/conversation.md\n@@ -1,8 +1,9 @@\n # Conversation API\n \n-* Base endpoint for API v1 is: `/ocs/v2.php/apps/spreed/api/v1`\n-* Base endpoint for API v2 is: `/ocs/v2.php/apps/spreed/api/v2`\n-* Base endpoint for API v3 is: `/ocs/v2.php/apps/spreed/api/v3`\n+* API v1: 🏁 Removed with API v4\n+* API v2: 🏁 Removed with API v4\n+* API v3: 🏁 Removed with API v4\n+* API v4: Base endpoint `/ocs/v2.php/apps/spreed/api/v4`\n \n ## Get user´s conversations\n \n@@ -29,37 +30,37 @@\n         `type` | int | * | See list of conversation types in the [constants list](constants.md#conversation-types)\n         `name` | string | * | Name of the conversation (can also be empty)\n         `displayName` | string | * | `name` if non empty, otherwise it falls back to a list of participants\n-        `description` | string | v3 | Description of the conversation (can also be empty) (only available with `room-description` capability)\n+        `description` | string | * | Description of the conversation (can also be empty) (only available with `room-description` capability)\n         `participantType` | int | * | Permissions level of the current user\n-        `attendeeId` | int | v3 | Unique attendee id\n-        `attendeePin` | string | v3 | Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)\n-        `actorType` | string | v3 | Currently known `users|guests|emails|groups`\n-        `actorId` | string | v3 | The unique identifier for the given actor type\n-        `participantInCall` | bool | 🏴 v1 | **Deprecated:** ~~Flag if a **random** active session of the current user (might be from a different device) is in the call (deprecated, use `participantFlags` instead)~~\n-        `participantFlags` | int | * | **Deprecated:** ~~Flags of a **random** active session of the current user (might be from a different device) (only available with `in-call-flags` capability)~~\n+        `attendeeId` | int | * | Unique attendee id\n+        `attendeePin` | string | * | Unique dial-in authentication code for this user, when the conversation has SIP enabled (see `sipEnabled` attribute)\n+        `actorType` | string | * | Currently known `users|guests|emails|groups`\n+        `actorId` | string | * | The unique identifier for the given actor type\n+        `participantInCall` | 🏁 | 🏁 | **Removed:** use `participantFlags` instead\n+        `participantFlags` | int | * | "In call" flags of the user\'s session making the request (only available with `in-call-flags` capability)\n         `readOnly` | int | * | Read-only state for the current user (only available with `read-only-rooms` capability)\n         `listable` | int | * | Listable scope for the room (only available with `listable-rooms` capability)\n-        `count` | int | 🏴 v1 | **Deprecated:** ~~Number of active users~~ - always returns `0`\n-        `numGuests` | int | 🏴 v1 | Number of active guests\n-        `lastPing` | int | * | **Deprecated:** ~~Timestamp of the last ping of a **random** active session of the current user (might be from a different device)~~\n-        `sessionId` | string | * | `\'0\'` if not connected, otherwise an up to 512 character long string that is the identifier of a **random** active session of the current user (might be from a different device), should only be used to pre-check if the user joined in another device, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](participant.md#get-list-of-participants-in-a-conversation)\n+        `count` | 🏁 | 🏁 | **Removed:** Count the users on the [Get list of participants in a conversation](participant.md#get-list-of-participants-in-a-conversation) endpoint\n+        `numGuests` | 🏁 | 🏁 | **Removed:** Count the guests on the [Get list of participants in a conversation](participant.md#get-list-of-participants-in-a-conversation) endpoin\n+        `lastPing` | int | * | Timestamp of the user\'s session making the request\n+        `sessionId` | string | * | `\'0\'` if not connected, otherwise an up to 512 character long string that is the identifier of the user\'s session making the request. Should only be used to pre-check if the user joined already with this session, but this might be outdated by the time of usage, so better check via [Get list of participants in a conversation](participant.md#get-list-of-participants-in-a-conversation)\n         `hasPassword` | bool | * | Flag if the conversation has a password\n         `hasCall` | bool | * | Flag if the conversation has an active call\n-        `callFlag` | int | v3 | Combined flag of all participants in the current call (see [constants list](constants.md#participant-in-call-flag), only available with `conversation-call-flags` capability)\n+        `callFlag` | int | * | Combined flag of all participants in the current call (see [constants list](constants.md#participant-in-call-flag), only available with `conversation-call-flags` capability)\n         `canStartCall` | bool | * | Flag if the user can start a new call in this conversation (joining is always possible) (only available with `start-call-flag` capability)\n-        `canDeleteConversation` | bool | v2 | Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)\n-        `canLeaveConversation` | bool | v2 | Flag if the user can leave the conversation (not possible for the last user with moderator permissions)\n+        `canDeleteConversation` | bool | * | Flag if the user can delete the conversation for everyone (not possible without moderator permissions or in one-to-one conversations)\n+        `canLeaveConversation` | bool | * | Flag if the user can leave the conversation (not possible for the last user with moderator permissions)\n         `lastActivity` | int | * | Timestamp of the last activity in the conversation, in seconds and UTC time zone\n         `isFavorite` | bool | * | Flag if the conversation is favorited by the user\n-        `notificationLevel` | int | * | The notification level for the user (one of `Participant::NOTIFY_*` (1-3))\n-        `lobbyState` | int | * | Webinary lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability)\n+        `notificationLevel` | int | * | The notification level for the user (See [Participant notification levels](constants.md#Participant-notification-levels))\n+        `lobbyState` | int | * | Webinar lobby restriction (0-1), if the participant is a moderator they can always join the conversation (only available with `webinary-lobby` capability) (See [Webinar lobby states](constants.md#webinar-lobby-states))\n         `lobbyTimer` | int | * | Timestamp when the lobby will be automatically disabled (only available with `webinary-lobby` capability)\n-        `sipEnabled` | int | v3 | SIP enable status (0-1)\n-        `canEnableSIP` | int | v3 | Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation\n+        `sipEnabled` | int | * | SIP enable status (0-1)\n+        `canEnableSIP` | int | * | Whether the given user can enable SIP for this conversation. Note that when the token is not-numeric only, SIP can not be enabled even if the user is permitted and a moderator of the conversation\n         `unreadMessages` | int | * | Number of unread chat messages in the conversation (only available with `chat-v2` capability)\n         `unreadMention` | bool | * | Flag if the user was mentioned since their last visit\n         `lastReadMessage` | int | * | ID of the last read message in a room (only available with `chat-read-marker` capability)\n-        `lastCommonReadMessage` | int | v3 | ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)\n+        `lastCommonReadMessage` | int | * | ID of the last message read by every user that has read privacy set to public in a room. When the user themself has it set to private the value is `0` (only available with `chat-read-status` capability)\n         `lastMessage` | message | * | Last message in a conversation if available, otherwise empty\n         `objectType` | string | * | The type of object that the conversation is associated with; "share:password" if the conversation is used to request a password for a share, otherwise empty\n         `objectId` | string | * | Share token if "objectType" is "share:password", otherwise empty\n@@ -207,7 +208,7 @@\n \n     field | type | Description\n     ------|------|------------\n-    `state` | int | New state for the conversation\n+    `state` | int | New state for the conversation, see [constants list](constants.md#read-only-states)\n \n * Response:\n     - Status code:\n@@ -233,25 +234,9 @@\n         + `403 Forbidden` When the conversation is not a public conversation\n         + `404 Not Found` When the conversation could not be found for the participant\n \n-## Set conversation password\n-\n-* Method: `PUT`\n-* Endpoint: `/room/{token}/password`\n-* Data:\n-\n-    field | type | Description\n-    ------|------|------------\n-    `password` | string | Set a new password for the conversation\n-\n-* Response:\n-    - Status code:\n-        + `200 OK`\n-        + `403 Forbidden` When the current user is not a moderator/owner\n-        + `403 Forbidden` When the conversation is not a public conversation\n-        + `404 Not Found` When the conversation could not be found for the participant\n-\n ## Add conversation to favorites\n \n+* Required capability: `favorites`\n * Method: `POST`\n * Endpoint: `/room/{token}/favorite`\n \n@@ -263,6 +248,7 @@\n \n ## Remove conversation from favorites\n \n+* Required capability: `favorites`\n * Method: `DELETE`\n * Endpoint: `/room/{token}/favorite`\n \n@@ -274,6 +260,7 @@\n \n ## Set notification level\n \n+* Required capability: `notification-levels`\n * Method: `POST`\n * Endpoint: `/room/{token}/notify`\n * Data:\ndiff --git a/docs/participant.md b/docs/participant.md\nindex 02afee959..86cfb240c 100644\n--- a/docs/participant.md\n+++ b/docs/participant.md\n@@ -1,8 +1,9 @@\n # Participant API\n \n-* Base endpoint for API v1 is: `/ocs/v2.php/apps/spreed/api/v1`\n-* Base endpoint for API v2 is: `/ocs/v2.php/apps/spreed/api/v2`\n-* Base endpoint for API v3 is: `/ocs/v2.php/apps/spreed/api/v3`\n+* API v1: 🏁 Removed with API v4\n+* API v2: 🏁 Removed with API v4\n+* API v3: 🏁 Removed with API v4\n+* API v4: Base endpoint `/ocs/v2.php/apps/spreed/api/v4`\n \n ## Get list of participants in a conversation\n \n@@ -26,10 +27,9 @@\n \n         field | type | API | Description\n         ------|------|-----|------------\n-        `userId` | string | v1 + v2 only | Is empty for guests\n-        `attendeeId` | int | v3 | Unique attendee id\n-        `actorType` | string | v3 | Currently known `users|guests|emails|groups`\n-        `actorId` | string | v3 | The unique identifier for the given actor type\n+        `attendeeId` | int | * | Unique attendee id\n+        `actorType` | string | * | Currently known `users|guests|emails|groups`\n+        `actorId` | string | * | The unique identifier for the given actor type\n         `displayName` | string | * | Can be empty for guests\n         `participantType` | int | * | Permissions level of the participant (see [constants list](constants.md#participant-types))\n         `lastPing` | int | * | Timestamp of the last ping of the user (should be used for sorting)\n@@ -67,7 +67,6 @@\n \n ## Delete an attendee by id from a conversation\n \n-* API: Only `v3` or later\n * Method: `DELETE`\n * Endpoint: `/room/{token}/attendees`\n * Data:\n@@ -86,27 +85,6 @@\n         + `404 Not Found` When the conversation could not be found for the participant\n         + `404 Not Found` When the participant to remove could not be found\n \n-## Delete a participant from a conversation\n-\n-* API: Only `v1` and `v2` (Use [Delete an attendee by id from a conversation](#delete-an-attendee-by-id-from-a-conversation) in later version)\n-* Method: `DELETE`\n-* Endpoint: `/room/{token}/participants`\n-* Data:\n-\n-    field | type | Description\n-    ------|------|------------\n-    `participant` | string | User to remove\n-\n-* Response:\n-    - Status code:\n-        + `200 OK`\n-        + `400 Bad Request` When the participant is a moderator or owner\n-        + `400 Bad Request` When there are no other moderators or owners left\n-        + `403 Forbidden` When the current user is not a moderator or owner\n-        + `403 Forbidden` When the participant to remove is an owner\n-        + `404 Not Found` When the conversation could not be found for the participant\n-        + `404 Not Found` When the participant to remove could not be found\n-\n ## Remove yourself from a conversation\n \n * Method: `DELETE`\n@@ -118,25 +96,6 @@\n         + `400 Bad Request` When the participant is a moderator or owner and there are no other moderators or owners left.\n         + `404 Not Found` When the conversation could not be found for the participant\n \n-## Remove a guest from a conversation\n-\n-* API: Only `v1` and `v2` (Use [Delete an attendee by id from a conversation](#delete-an-attendee-by-id-from-a-conversation) in later versions)\n-* Method: `DELETE`\n-* Endpoint: `/room/{token}/participants/guests`\n-* Data:\n-\n-    field | type | Description\n-    ------|------|------------\n-    `participant` | string | Session ID of the guest to remove\n-\n-* Response:\n-    - Status code:\n-        + `200 OK`\n-        + `400 Bad Request` When the target participant is not a guest\n-        + `403 Forbidden` When the current user is not a moderator or owner\n-        + `404 Not Found` When the conversation could not be found for the participant\n-        + `404 Not Found` When the target participant could not be found\n-\n ## Join a conversation (available for call and chat)\n \n * Method: `POST`\n@@ -145,7 +104,7 @@\n \n     field | type | Description\n     ------|------|------------\n-    `password` | string | Optional: Password is only required for users which are of type `4` or `5` and only when the conversation has `hasPassword` set to true.\n+    `password` | string | Optional: Password is only required for users which are self joined or guests and only when the conversation has `hasPassword` set to true.\n     `force` | bool | If set to `false` and the user has an active session already a `409 Conflict` will be returned (Default: true - to keep the old behaviour)\n \n * Response:\n@@ -153,7 +112,7 @@\n         + `200 OK`\n         + `403 Forbidden` When the password is required and didn\'t match\n         + `404 Not Found` When the conversation could not be found for the participant\n-        + `409 Conflict` When the user already has an active session in the conversation. The suggested behaviour is to ask the user whether they want to kill the old session and force join unless the last ping is older than 60 seconds or older than 40 seconds when the conflicting session is not marked as in a call.\n+        + `409 Conflict` When the user already has an active Talk session in the conversation with this Nextcloud session. The suggested behaviour is to ask the user whether they want to kill the old session and force join unless the last ping is older than 60 seconds or older than 40 seconds when the conflicting session is not marked as in a call.\n \n     - Data in case of `200 OK`: See array definition in [Get user´s conversations](conversation.md#get-user-s-conversations)\n \n@@ -174,7 +133,7 @@\n \n     field | type | Description\n     ------|------|------------\n-    `attendeeId` | int or null | Attendee id can be used for guests and users\n+    `attendeeId` | int or null | Attendee id can be used for guests and users, not setting it will resend all invitations\n \n * Response:\n     - Status code:\n@@ -198,11 +157,9 @@\n * Endpoint: `/room/{token}/moderators`\n * Data:\n \n-    field | type | API | Description\n-    ------|------|-----|------------\n-    `participant` | string or null | v1 + v2 | User to demote\n-    `sessionId` | string or null | v1 + v2 | Guest session to demote\n-    `attendeeId` | int or null | v3 | Attendee id can be used for guests and users\n+    field | type | Description\n+    ------|------|------------\n+    `attendeeId` | int | Attendee id can be used for guests and users\n \n * Response:\n     - Status code:\n@@ -219,11 +176,9 @@\n * Endpoint: `/room/{token}/moderators`\n * Data:\n \n-    field | type | API | Description\n-    ------|------|-----|------------\n-    `participant` | string or null | v1 + v2 | User to demote\n-    `sessionId` | string or null | v1 + v2 | Guest session to demote\n-    `attendeeId` | int or null | v3 | Attendee id can be used for guests and users\n+    field | type | Description\n+    ------|------|------------\n+    `attendeeId` | int | Attendee id can be used for guests and users\n \n * Response:\n     - Status code:\n@@ -238,7 +193,6 @@\n \n Note: This is only allowed with validate SIP bridge requests\n \n-* API: Only `v3` or later\n * Required capability: `sip-support`\n * Method: `GET`\n * Endpoint: `/room/{token}/pin/{pin}`\ndiff --git a/docs/webinar.md b/docs/webinar.md\nindex 3e0d9c119..d87bd27da 100644\n--- a/docs/webinar.md\n+++ b/docs/webinar.md\n@@ -8,9 +8,10 @@ Group and public conversations can be used to host webinars. Those online meetin\n \n ## Base endpoint\n \n-* Base endpoint for API v1 is: `/ocs/v2.php/apps/spreed/api/v1`\n-* Base endpoint for API v2 is: `/ocs/v2.php/apps/spreed/api/v2`\n-* Base endpoint for API v3 is: `/ocs/v2.php/apps/spreed/api/v3`\n+* API v1: 🏁 Removed with API v4\n+* API v2: 🏁 Removed with API v4\n+* API v3: 🏁 Removed with API v4\n+* API v4: Base endpoint `/ocs/v2.php/apps/spreed/api/v4`\n \n ## Set lobby for a conversation\n \ndiff --git a/lib/Controller/CallController.php b/lib/Controller/CallController.php\nindex 7bfc48045..e638ca8d4 100644\n--- a/lib/Controller/CallController.php\n+++ b/lib/Controller/CallController.php\n@@ -72,42 +72,28 @@ class CallController extends AEnvironmentAwareController {\n \t\t$participants = $this->participantService->getParticipantsInCall($this->room, $timeout);\n \n \t\tforeach ($participants as $participant) {\n-\t\t\tif ($this->getAPIVersion() >= 3) {\n-\t\t\t\t$displayName = $participant->getAttendee()->getActorId();\n-\t\t\t\tif ($participant->getAttendee()->getActorType() === Attendee::ACTOR_USERS) {\n-\t\t\t\t\tif ($participant->getAttendee()->getDisplayName()) {\n-\t\t\t\t\t\t$displayName = $participant->getAttendee()->getDisplayName();\n-\t\t\t\t\t} else {\n-\t\t\t\t\t\t$user = $this->userManager->get($participant->getAttendee()->getActorId());\n-\t\t\t\t\t\tif ($user instanceof IUser) {\n-\t\t\t\t\t\t\t$displayName = $user->getDisplayName();\n-\t\t\t\t\t\t}\n-\t\t\t\t\t}\n-\t\t\t\t} else {\n+\t\t\t$displayName = $participant->getAttendee()->getActorId();\n+\t\t\tif ($participant->getAttendee()->getActorType() === Attendee::ACTOR_USERS) {\n+\t\t\t\tif ($participant->getAttendee()->getDisplayName()) {\n \t\t\t\t\t$displayName = $participant->getAttendee()->getDisplayName();\n+\t\t\t\t} else {\n+\t\t\t\t\t$user = $this->userManager->get($participant->getAttendee()->getActorId());\n+\t\t\t\t\tif ($user instanceof IUser) {\n+\t\t\t\t\t\t$displayName = $user->getDisplayName();\n+\t\t\t\t\t}\n \t\t\t\t}\n-\n-\t\t\t\t$result[] = [\n-\t\t\t\t\t\'actorType\' => $participant->getAttendee()->getActorType(),\n-\t\t\t\t\t\'actorId\' => $participant->getAttendee()->getActorId(),\n-\t\t\t\t\t\'displayName\' => $displayName,\n-\t\t\t\t\t\'token\' => $this->room->getToken(),\n-\t\t\t\t\t\'lastPing\' => $participant->getSession()->getLastPing(),\n-\t\t\t\t\t\'sessionId\' => $participant->getSession()->getSessionId(),\n-\t\t\t\t];\n \t\t\t} else {\n-\t\t\t\t$userId = \'\';\n-\t\t\t\tif ($participant->getAttendee()->getActorType() === Attendee::ACTOR_USERS) {\n-\t\t\t\t\t$userId = $participant->getAttendee()->getActorId();\n-\t\t\t\t}\n-\n-\t\t\t\t$result[] = [\n-\t\t\t\t\t\'userId\' => $userId,\n-\t\t\t\t\t\'token\' => $this->room->getToken(),\n-\t\t\t\t\t\'lastPing\' => $participant->getSession()->getLastPing(),\n-\t\t\t\t\t\'sessionId\' => $participant->getSession()->getSessionId(),\n-\t\t\t\t];\n+\t\t\t\t$displayName = $participant->getAttendee()->getDisplayName();\n \t\t\t}\n+\n+\t\t\t$result[] = [\n+\t\t\t\t\'actorType\' => $participant->getAttendee()->getActorType(),\n+\t\t\t\t\'actorId\' => $participant->getAttendee()->getActorId(),\n+\t\t\t\t\'displayName\' => $displayName,\n+\t\t\t\t\'token\' => $this->room->getToken(),\n+\t\t\t\t\'lastPing\' => $participant->getSession()->getLastPing(),\n+\t\t\t\t\'sessionId\' => $participant->getSession()->getSessionId(),\n+\t\t\t];\n \t\t}\n \n \t\treturn new DataResponse($result);\ndiff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php\nindex 99fb4e3cf..2cf1b4382 100644\n--- a/lib/Controller/RoomController.php\n+++ b/lib/Controller/RoomController.php\n@@ -257,10 +257,6 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\treturn new DataResponse([], Http::STATUS_UNAUTHORIZED);\n \t\t}\n \n-\t\tif ($isSIPBridgeRequest && $this->getAPIVersion() < 3) {\n-\t\t\treturn new DataResponse([], Http::STATUS_BAD_REQUEST);\n-\t\t}\n-\n \t\t// The SIP bridge only needs room details (public, sip enabled, lobby state, etc)\n \t\t$includeLastMessage = !$isSIPBridgeRequest;\n \n@@ -334,209 +330,7 @@ class RoomController extends AEnvironmentAwareController {\n \t * @throws RoomNotFoundException\n \t */\n \tprotected function formatRoom(Room $room, ?Participant $currentParticipant, bool $isSIPBridgeRequest = false): array {\n-\t\tif ($this->getAPIVersion() >= 2) {\n-\t\t\treturn $this->formatRoomV2andV3($room, $currentParticipant, $isSIPBridgeRequest);\n-\t\t}\n-\n-\t\treturn $this->formatRoomV1($room, $currentParticipant);\n-\t}\n-\n-\t/**\n-\t * @param Room $room\n-\t * @param Participant|null $currentParticipant\n-\t * @return array\n-\t * @throws RoomNotFoundException\n-\t */\n-\tprotected function formatRoomV1(Room $room, ?Participant $currentParticipant): array {\n-\t\t$roomData = [\n-\t\t\t\'id\' => $room->getId(),\n-\t\t\t\'token\' => $room->getToken(),\n-\t\t\t\'type\' => $room->getType(),\n-\t\t\t\'name\' => \'\',\n-\t\t\t\'displayName\' => \'\',\n-\t\t\t\'objectType\' => \'\',\n-\t\t\t\'objectId\' => \'\',\n-\t\t\t\'participantType\' => Participant::GUEST,\n-\t\t\t// Deprecated, use participantFlags instead.\n-\t\t\t\'participantInCall\' => false,\n-\t\t\t\'participantFlags\' => Participant::FLAG_DISCONNECTED,\n-\t\t\t\'readOnly\' => Room::READ_WRITE,\n-\t\t\t\'count\' => 0,\n-\t\t\t\'hasPassword\' => $room->hasPassword(),\n-\t\t\t\'hasCall\' => false,\n-\t\t\t\'canStartCall\' => false,\n-\t\t\t\'lastActivity\' => 0,\n-\t\t\t\'lastReadMessage\' => 0,\n-\t\t\t\'unreadMessages\' => 0,\n-\t\t\t\'unreadMention\' => false,\n-\t\t\t\'isFavorite\' => false,\n-\t\t\t\'notificationLevel\' => Participant::NOTIFY_NEVER,\n-\t\t\t\'lobbyState\' => Webinary::LOBBY_NONE,\n-\t\t\t\'lobbyTimer\' => 0,\n-\t\t\t\'lastPing\' => 0,\n-\t\t\t\'sessionId\' => \'0\',\n-\t\t\t\'participants\' => [],\n-\t\t\t\'numGuests\' => 0,\n-\t\t\t\'guestList\' => \'\',\n-\t\t\t\'lastMessage\' => [],\n-\t\t];\n-\n-\t\tif (!$currentParticipant instanceof Participant) {\n-\t\t\treturn $roomData;\n-\t\t}\n-\n-\t\t$attendee = $currentParticipant->getAttendee();\n-\t\t$userId = $attendee->getActorType() === Attendee::ACTOR_USERS ? $attendee->getActorId() : \'\';\n-\n-\t\t$lastActivity = $room->getLastActivity();\n-\t\tif ($lastActivity instanceof \\DateTimeInterface) {\n-\t\t\t$lastActivity = $lastActivity->getTimestamp();\n-\t\t} else {\n-\t\t\t$lastActivity = 0;\n-\t\t}\n-\n-\t\t$lobbyTimer = $room->getLobbyTimer();\n-\t\tif ($lobbyTimer instanceof \\DateTimeInterface) {\n-\t\t\t$lobbyTimer = $lobbyTimer->getTimestamp();\n-\t\t} else {\n-\t\t\t$lobbyTimer = 0;\n-\t\t}\n-\n-\t\t$roomData = array_merge($roomData, [\n-\t\t\t\'name\' => $room->getName(),\n-\t\t\t\'displayName\' => $room->getDisplayName($userId),\n-\t\t\t\'objectType\' => $room->getObjectType(),\n-\t\t\t\'objectId\' => $room->getObjectId(),\n-\t\t\t\'participantType\' => $attendee->getParticipantType(),\n-\t\t\t\'readOnly\' => $room->getReadOnly(),\n-\t\t\t\'count\' => 0, // Deprecated, remove in future API version\n-\t\t\t\'hasCall\' => $room->getActiveSince() instanceof \\DateTimeInterface,\n-\t\t\t\'lastActivity\' => $lastActivity,\n-\t\t\t\'isFavorite\' => $attendee->isFavorite(),\n-\t\t\t\'notificationLevel\' => $attendee->getNotificationLevel(),\n-\t\t\t\'lobbyState\' => $room->getLobbyState(),\n-\t\t\t\'lobbyTimer\' => $lobbyTimer,\n-\t\t]);\n-\n-\t\t$session = $currentParticipant->getSession();\n-\t\tif ($session instanceof Session) {\n-\t\t\t$roomData = array_merge($roomData, [\n-\t\t\t\t// Deprecated, use participantFlags instead.\n-\t\t\t\t\'participantInCall\' => ($session->getInCall() & Participant::FLAG_IN_CALL) !== 0,\n-\t\t\t\t\'participantFlags\' => $session->getInCall(),\n-\t\t\t\t\'lastPing\' => $session->getLastPing(),\n-\t\t\t\t\'sessionId\' => $session->getSessionId(),\n-\t\t\t]);\n-\t\t}\n-\n-\t\tif ($roomData[\'notificationLevel\'] === Participant::NOTIFY_DEFAULT) {\n-\t\t\tif ($currentParticipant->isGuest()) {\n-\t\t\t\t$roomData[\'notificationLevel\'] = Participant::NOTIFY_NEVER;\n-\t\t\t} elseif ($room->getType() === Room::ONE_TO_ONE_CALL) {\n-\t\t\t\t$roomData[\'notificationLevel\'] = Participant::NOTIFY_ALWAYS;\n-\t\t\t} else {\n-\t\t\t\t$adminSetting = (int) $this->config->getAppValue(\'spreed\', \'default_group_notification\', Participant::NOTIFY_DEFAULT);\n-\t\t\t\tif ($adminSetting === Participant::NOTIFY_DEFAULT) {\n-\t\t\t\t\t$roomData[\'notificationLevel\'] = Participant::NOTIFY_MENTION;\n-\t\t\t\t} else {\n-\t\t\t\t\t$roomData[\'notificationLevel\'] = $adminSetting;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t}\n-\n-\t\tif ($room->getLobbyState() === Webinary::LOBBY_NON_MODERATORS &&\n-\t\t\t!$currentParticipant->hasModeratorPermissions()) {\n-\t\t\t// No participants and chat messages for users in the lobby.\n-\t\t\t$roomData[\'hasCall\'] = false;\n-\t\t\treturn $roomData;\n-\t\t}\n-\n-\t\t$roomData[\'canStartCall\'] = $currentParticipant->canStartCall($this->config);\n-\n-\t\tif ($userId !== \'\') {\n-\t\t\t$currentUser = $this->userManager->get($userId);\n-\t\t\tif ($currentUser instanceof IUser) {\n-\t\t\t\t$lastReadMessage = $attendee->getLastReadMessage();\n-\t\t\t\tif ($lastReadMessage === -1) {\n-\t\t\t\t\t/*\n-\t\t\t\t\t * Because the migration from the old comment_read_markers was\n-\t\t\t\t\t * not possible in a programmatic way with a reasonable O(1) or O(n)\n-\t\t\t\t\t * but only with O(user×chat), we do the conversion here.\n-\t\t\t\t\t */\n-\t\t\t\t\t$lastReadMessage = $this->chatManager->getLastReadMessageFromLegacy($room, $currentUser);\n-\t\t\t\t\t$this->participantService->updateLastReadMessage($currentParticipant, $lastReadMessage);\n-\t\t\t\t}\n-\t\t\t\t$roomData[\'unreadMessages\'] = $this->chatManager->getUnreadCount($room, $lastReadMessage);\n-\n-\t\t\t\t$lastMention = $attendee->getLastMentionMessage();\n-\t\t\t\t$roomData[\'unreadMention\'] = $lastMention !== 0 && $lastReadMessage < $lastMention;\n-\t\t\t\t$roomData[\'lastReadMessage\'] = $lastReadMessage;\n-\t\t\t}\n-\t\t}\n-\n-\t\t$numActiveGuests = 0;\n-\t\t$cleanGuests = false;\n-\t\t$participantList = [];\n-\t\t$participants = $this->participantService->getSessionsAndParticipantsForRoom($room); // FIXME NEEDS the session but can potentially kill APIv1?\n-\t\tuasort($participants, function (Participant $participant1, Participant $participant2) {\n-\t\t\t$s1 = $participant1->getSession() ? $participant1->getSession()->getLastPing() : 0;\n-\t\t\t$s2 = $participant2->getSession() ? $participant2->getSession()->getLastPing() : 0;\n-\t\t\treturn $s2 - $s1;\n-\t\t});\n-\n-\t\tforeach ($participants as $participant) {\n-\t\t\t/** @var Participant $participant */\n-\t\t\tif ($participant->isGuest()) {\n-\t\t\t\tif ($participant->getSession()) {\n-\t\t\t\t\tif ($participant->getSession()->getLastPing() <= $this->timeFactory->getTime() - 100) {\n-\t\t\t\t\t\t$cleanGuests = true;\n-\t\t\t\t\t} else {\n-\t\t\t\t\t\t$numActiveGuests++;\n-\t\t\t\t\t}\n-\t\t\t\t}\n-\t\t\t} elseif ($participant->getAttendee()->getActorType() === Attendee::ACTOR_USERS) {\n-\t\t\t\t$attendee = $participant->getAttendee();\n-\t\t\t\t$session = $participant->getSession();\n-\t\t\t\t$user = $this->userManager->get($attendee->getActorId());\n-\t\t\t\tif ($user instanceof IUser) {\n-\t\t\t\t\t$participantList[(string)$user->getUID()] = [\n-\t\t\t\t\t\t\'name\' => $user->getDisplayName(),\n-\t\t\t\t\t\t\'type\' => $attendee->getParticipantType(),\n-\t\t\t\t\t\t\'call\' => $session ? $session->getInCall() : Participant::FLAG_DISCONNECTED,\n-\t\t\t\t\t\t\'sessionId\' => $session ? $session->getSessionId() : \'0\',\n-\t\t\t\t\t];\n-\n-\t\t\t\t\tif ($room->getType() === Room::ONE_TO_ONE_CALL &&\n-\t\t\t\t\t\t  $user->getUID() !== $currentParticipant->getAttendee()->getActorId()) {\n-\t\t\t\t\t\t// FIXME This should not be done, but currently all the clients use it to get the avatar of the user …\n-\t\t\t\t\t\t$roomData[\'name\'] = $user->getUID();\n-\t\t\t\t\t}\n-\t\t\t\t}\n-\n-\t\t\t\tif ($session && $session->getLastPing() <= $this->timeFactory->getTime() - 100) {\n-\t\t\t\t\t$this->participantService->leaveRoomAsSession($room, $participant);\n-\t\t\t\t}\n-\t\t\t}\n-\t\t}\n-\n-\t\tif ($cleanGuests) {\n-\t\t\t$this->participantService->cleanGuestParticipants($room);\n-\t\t}\n-\n-\t\t$lastMessage = $room->getLastMessage();\n-\t\tif ($lastMessage instanceof IComment) {\n-\t\t\t$lastMessage = $this->formatLastMessage($room, $currentParticipant, $lastMessage);\n-\t\t} else {\n-\t\t\t$lastMessage = [];\n-\t\t}\n-\n-\t\t$roomData = array_merge($roomData, [\n-\t\t\t\'participants\' => $participantList,\n-\t\t\t\'numGuests\' => $numActiveGuests,\n-\t\t\t\'lastMessage\' => $lastMessage,\n-\t\t]);\n-\n-\t\treturn $roomData;\n+\t\treturn $this->formatRoomV4($room, $currentParticipant, $isSIPBridgeRequest);\n \t}\n \n \t/**\n@@ -546,7 +340,7 @@ class RoomController extends AEnvironmentAwareController {\n \t * @return array\n \t * @throws RoomNotFoundException\n \t */\n-\tprotected function formatRoomV2andV3(Room $room, ?Participant $currentParticipant, bool $isSIPBridgeRequest = false): array {\n+\tprotected function formatRoomV4(Room $room, ?Participant $currentParticipant, bool $isSIPBridgeRequest = false): array {\n \t\t$roomData = [\n \t\t\t\'id\' => $room->getId(),\n \t\t\t\'token\' => $room->getToken(),\n@@ -575,21 +369,17 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\t\'sessionId\' => \'0\',\n \t\t\t\'guestList\' => \'\',\n \t\t\t\'lastMessage\' => [],\n+\t\t\t\'sipEnabled\' => Webinary::SIP_DISABLED,\n+\t\t\t\'actorType\' => \'\',\n+\t\t\t\'actorId\' => \'\',\n+\t\t\t\'attendeeId\' => 0,\n+\t\t\t\'canEnableSIP\' => false,\n+\t\t\t\'attendeePin\' => \'\',\n+\t\t\t\'description\' => \'\',\n+\t\t\t\'lastCommonReadMessage\' => 0,\n+\t\t\t\'listable\' => Room::LISTABLE_NONE,\n+\t\t\t\'callFlag\' => Participant::FLAG_DISCONNECTED,\n \t\t];\n-\t\tif ($this->getAPIVersion() >= 3) {\n-\t\t\t$roomData = array_merge($roomData, [\n-\t\t\t\t\'sipEnabled\' => Webinary::SIP_DISABLED,\n-\t\t\t\t\'actorType\' => \'\',\n-\t\t\t\t\'actorId\' => \'\',\n-\t\t\t\t\'attendeeId\' => 0,\n-\t\t\t\t\'canEnableSIP\' => false,\n-\t\t\t\t\'attendeePin\' => \'\',\n-\t\t\t\t\'description\' => \'\',\n-\t\t\t\t\'lastCommonReadMessage\' => 0,\n-\t\t\t\t\'listable\' => Room::LISTABLE_NONE,\n-\t\t\t\t\'callFlag\' => Participant::FLAG_DISCONNECTED,\n-\t\t\t]);\n-\t\t}\n \n \t\t$lastActivity = $room->getLastActivity();\n \t\tif ($lastActivity instanceof \\DateTimeInterface) {\n@@ -645,32 +435,28 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\t\'notificationLevel\' => $attendee->getNotificationLevel(),\n \t\t\t\'lobbyState\' => $room->getLobbyState(),\n \t\t\t\'lobbyTimer\' => $lobbyTimer,\n+\t\t\t\'actorType\' => $attendee->getActorType(),\n+\t\t\t\'actorId\' => $attendee->getActorId(),\n+\t\t\t\'attendeeId\' => $attendee->getId(),\n+\t\t\t\'description\' => $room->getDescription(),\n+\t\t\t\'listable\' => $room->getListable(),\n \t\t]);\n-\t\tif ($this->getAPIVersion() >= 3) {\n-\t\t\tif ($this->talkConfig->isSIPConfigured()) {\n-\t\t\t\t$roomData[\'sipEnabled\'] = $room->getSIPEnabled();\n-\t\t\t\tif ($room->getSIPEnabled() === Webinary::SIP_ENABLED) {\n-\t\t\t\t\t// Generate a PIN if the attendee is a user and doesn\'t have one.\n-\t\t\t\t\t$this->participantService->generatePinForParticipant($room, $currentParticipant);\n-\n-\t\t\t\t\t$roomData[\'attendeePin\'] = $attendee->getPin();\n-\t\t\t\t}\n+\n+\t\tif ($currentParticipant->getAttendee()->getReadPrivacy() === Participant::PRIVACY_PUBLIC) {\n+\t\t\tif (isset($this->commonReadMessages[$room->getId()])) {\n+\t\t\t\t$roomData[\'lastCommonReadMessage\'] = $this->commonReadMessages[$room->getId()];\n+\t\t\t} else {\n+\t\t\t\t$roomData[\'lastCommonReadMessage\'] = $this->chatManager->getLastCommonReadMessage($room);\n \t\t\t}\n+\t\t}\n \n-\t\t\t$roomData = array_merge($roomData, [\n-\t\t\t\t\'actorType\' => $attendee->getActorType(),\n-\t\t\t\t\'actorId\' => $attendee->getActorId(),\n-\t\t\t\t\'attendeeId\' => $attendee->getId(),\n-\t\t\t\t\'description\' => $room->getDescription(),\n-\t\t\t\t\'listable\' => $room->getListable(),\n-\t\t\t]);\n+\t\tif ($this->talkConfig->isSIPConfigured()) {\n+\t\t\t$roomData[\'sipEnabled\'] = $room->getSIPEnabled();\n+\t\t\tif ($room->getSIPEnabled() === Webinary::SIP_ENABLED) {\n+\t\t\t\t// Generate a PIN if the attendee is a user and doesn\'t have one.\n+\t\t\t\t$this->participantService->generatePinForParticipant($room, $currentParticipant);\n \n-\t\t\tif ($currentParticipant->getAttendee()->getReadPrivacy() === Participant::PRIVACY_PUBLIC) {\n-\t\t\t\tif (isset($this->commonReadMessages[$room->getId()])) {\n-\t\t\t\t\t$roomData[\'lastCommonReadMessage\'] = $this->commonReadMessages[$room->getId()];\n-\t\t\t\t} else {\n-\t\t\t\t\t$roomData[\'lastCommonReadMessage\'] = $this->chatManager->getLastCommonReadMessage($room);\n-\t\t\t\t}\n+\t\t\t\t$roomData[\'attendeePin\'] = $attendee->getPin();\n \t\t\t}\n \t\t}\n \n@@ -735,14 +521,12 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\t\t$roomData[\'canDeleteConversation\'] = $room->getType() !== Room::ONE_TO_ONE_CALL\n \t\t\t\t\t&& $currentParticipant->hasModeratorPermissions(false);\n \t\t\t\t$roomData[\'canLeaveConversation\'] = true;\n-\t\t\t\tif ($this->getAPIVersion() >= 3) {\n-\t\t\t\t\t$roomData[\'canEnableSIP\'] =\n-\t\t\t\t\t\t$this->talkConfig->isSIPConfigured()\n-\t\t\t\t\t\t&& !preg_match(Room::SIP_INCOMPATIBLE_REGEX, $room->getToken())\n-\t\t\t\t\t\t&& ($room->getType() === Room::GROUP_CALL || $room->getType() === Room::PUBLIC_CALL)\n-\t\t\t\t\t\t&& $currentParticipant->hasModeratorPermissions(false)\n-\t\t\t\t\t\t&& $this->talkConfig->canUserEnableSIP($currentUser);\n-\t\t\t\t}\n+\t\t\t\t$roomData[\'canEnableSIP\'] =\n+\t\t\t\t\t$this->talkConfig->isSIPConfigured()\n+\t\t\t\t\t&& !preg_match(Room::SIP_INCOMPATIBLE_REGEX, $room->getToken())\n+\t\t\t\t\t&& ($room->getType() === Room::GROUP_CALL || $room->getType() === Room::PUBLIC_CALL)\n+\t\t\t\t\t&& $currentParticipant->hasModeratorPermissions(false)\n+\t\t\t\t\t&& $this->talkConfig->canUserEnableSIP($currentUser);\n \t\t\t}\n \t\t}\n \n@@ -1162,22 +946,22 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\t\t\'lastPing\' => 0,\n \t\t\t\t\'sessionId\' => \'0\', // FIXME empty string or null?\n \t\t\t\t\'participantType\' => $participant->getAttendee()->getParticipantType(),\n+\t\t\t\t\'attendeeId\' => $attendeeId,\n+\t\t\t\t\'actorId\' => $participant->getAttendee()->getActorId(),\n+\t\t\t\t\'actorType\' => $participant->getAttendee()->getActorType(),\n+\t\t\t\t\'displayName\' => $participant->getAttendee()->getActorId(),\n+\t\t\t\t\'attendeePin\' => \'\',\n \t\t\t];\n-\t\t\tif ($this->getAPIVersion() >= 3) {\n-\t\t\t\t$result[\'attendeeId\'] = $attendeeId;\n-\t\t\t\t$result[\'actorId\'] = $participant->getAttendee()->getActorId();\n-\t\t\t\t$result[\'actorType\'] = $participant->getAttendee()->getActorType();\n-\t\t\t\t$result[\'attendeePin\'] = \'\';\n-\t\t\t\tif ($this->talkConfig->isSIPConfigured()\n-\t\t\t\t\t&& $this->room->getSIPEnabled() === Webinary::SIP_ENABLED\n-\t\t\t\t\t&& ($this->participant->hasModeratorPermissions(false)\n-\t\t\t\t\t\t|| $this->participant->getAttendee()->getId() === $participant->getAttendee()->getId())) {\n-\t\t\t\t\t// Generate a PIN if the attendee is a user and doesn\'t have one.\n-\t\t\t\t\t$this->participantService->generatePinForParticipant($this->room, $participant);\n-\n-\t\t\t\t\t$result[\'attendeePin\'] = (string) $participant->getAttendee()->getPin();\n-\t\t\t\t}\n+\t\t\tif ($this->talkConfig->isSIPConfigured()\n+\t\t\t\t&& $this->room->getSIPEnabled() === Webinary::SIP_ENABLED\n+\t\t\t\t&& ($this->participant->hasModeratorPermissions(false)\n+\t\t\t\t\t|| $this->participant->getAttendee()->getId() === $participant->getAttendee()->getId())) {\n+\t\t\t\t// Generate a PIN if the attendee is a user and doesn\'t have one.\n+\t\t\t\t$this->participantService->generatePinForParticipant($this->room, $participant);\n+\n+\t\t\t\t$result[\'attendeePin\'] = (string) $participant->getAttendee()->getPin();\n \t\t\t}\n+\n \t\t\tif ($participant->getSession() instanceof Session) {\n \t\t\t\t$result[\'inCall\'] = $participant->getSession()->getInCall();\n \t\t\t\t$result[\'lastPing\'] = $participant->getSession()->getLastPing();\n@@ -1190,9 +974,6 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\t\t\t$this->participantService->leaveRoomAsSession($this->room, $participant);\n \t\t\t\t}\n \n-\t\t\t\tif ($this->getAPIVersion() < 3) {\n-\t\t\t\t\t$result[\'userId\'] = $participant->getAttendee()->getActorId();\n-\t\t\t\t}\n \t\t\t\t$result[\'displayName\'] = $participant->getAttendee()->getDisplayName();\n \t\t\t\tif (!$result[\'displayName\']) {\n \t\t\t\t\t$user = $this->userManager->get($userId);\n@@ -1214,16 +995,7 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\t\t\tcontinue;\n \t\t\t\t}\n \n-\t\t\t\tif ($this->getAPIVersion() < 3) {\n-\t\t\t\t\t$result[\'userId\'] = \'\';\n-\t\t\t\t}\n \t\t\t\t$result[\'displayName\'] = $participant->getAttendee()->getDisplayName();\n-\t\t\t} elseif ($this->getAPIVersion() >= 3) {\n-\t\t\t\t// Other types are only reported on v3 or later\n-\t\t\t\t$result[\'displayName\'] = $participant->getAttendee()->getActorId();\n-\t\t\t} else {\n-\t\t\t\t// Skip unknown actor types\n-\t\t\t\tcontinue;\n \t\t\t}\n \n \t\t\t$results[$attendeeId] = $result;\n@@ -1357,47 +1129,6 @@ class RoomController extends AEnvironmentAwareController {\n \t\treturn new DataResponse([]);\n \t}\n \n-\t/**\n-\t * @PublicPage\n-\t * @RequireParticipant\n-\t *\n-\t * @param string $participant\n-\t * @return DataResponse\n-\t */\n-\tpublic function removeParticipantFromRoom(string $participant): DataResponse {\n-\t\t$attendee = $this->participant->getAttendee();\n-\t\tif ($attendee->getActorType() === Attendee::ACTOR_USERS && $attendee->getActorId() === $participant) {\n-\t\t\t// Removing self, abusing moderator power\n-\t\t\treturn $this->removeSelfFromRoomLogic($this->room, $this->participant);\n-\t\t}\n-\n-\t\tif (!$this->participant->hasModeratorPermissions()) {\n-\t\t\treturn new DataResponse([], Http::STATUS_FORBIDDEN);\n-\t\t}\n-\n-\t\tif ($this->room->getType() === Room::ONE_TO_ONE_CALL) {\n-\t\t\treturn new DataResponse([], Http::STATUS_BAD_REQUEST);\n-\t\t}\n-\n-\t\ttry {\n-\t\t\t$targetParticipant = $this->room->getParticipant($participant);\n-\t\t} catch (ParticipantNotFoundException $e) {\n-\t\t\treturn new DataResponse([], Http::STATUS_NOT_FOUND);\n-\t\t}\n-\n-\t\tif ($targetParticipant->getAttendee()->getParticipantType() === Participant::OWNER) {\n-\t\t\treturn new DataResponse([], Http::STATUS_FORBIDDEN);\n-\t\t}\n-\n-\t\t$targetUser = $this->userManager->get($participant);\n-\t\tif (!$targetUser instanceof IUser) {\n-\t\t\treturn new DataResponse([], Http::STATUS_NOT_FOUND);\n-\t\t}\n-\n-\t\t$this->participantService->removeUser($this->room, $targetUser, Room::PARTICIPANT_REMOVED);\n-\t\treturn new DataResponse([]);\n-\t}\n-\n \t/**\n \t * @NoAdminRequired\n \t * @RequireLoggedInParticipant\n@@ -1439,36 +1170,6 @@ class RoomController extends AEnvironmentAwareController {\n \t\treturn new DataResponse();\n \t}\n \n-\t/**\n-\t * @PublicPage\n-\t * @RequireModeratorParticipant\n-\t *\n-\t * @param string $participant\n-\t * @return DataResponse\n-\t */\n-\tpublic function removeGuestFromRoom(string $participant): DataResponse {\n-\t\ttry {\n-\t\t\t$targetParticipant = $this->room->getParticipantBySession($participant);\n-\t\t} catch (ParticipantNotFoundException $e) {\n-\t\t\treturn new DataResponse([], Http::STATUS_NOT_FOUND);\n-\t\t}\n-\n-\t\tif (!$targetParticipant->isGuest()) {\n-\t\t\treturn new DataResponse([], Http::STATUS_BAD_REQUEST);\n-\t\t}\n-\n-\t\t$targetSession = $targetParticipant->getSession();\n-\t\t$currentSession = $this->participant->getSession();\n-\t\tif ($targetSession instanceof Session\n-\t\t\t&& $currentSession instanceof Session\n-\t\t\t&& $targetSession->getSessionId() === $currentSession->getSessionId()) {\n-\t\t\treturn new DataResponse([], Http::STATUS_FORBIDDEN);\n-\t\t}\n-\n-\t\t$this->participantService->removeAttendee($this->room, $targetParticipant, Room::PARTICIPANT_REMOVED);\n-\t\treturn new DataResponse([]);\n-\t}\n-\n \t/**\n \t * @PublicPage\n \t * @RequireModeratorParticipant\n@@ -1711,47 +1412,35 @@ class RoomController extends AEnvironmentAwareController {\n \t * @PublicPage\n \t * @RequireModeratorParticipant\n \t *\n-\t * @param int|null $attendeeId\n-\t * @param string|null $participant\n-\t * @param string|null $sessionId\n+\t * @param int $attendeeId\n \t * @return DataResponse\n \t */\n-\tpublic function promoteModerator(?int $attendeeId, ?string $participant, ?string $sessionId): DataResponse {\n-\t\treturn $this->changeParticipantType($attendeeId, $participant, $sessionId, true);\n+\tpublic function promoteModerator(int $attendeeId): DataResponse {\n+\t\treturn $this->changeParticipantType($attendeeId, true);\n \t}\n \n \t/**\n \t * @PublicPage\n \t * @RequireModeratorParticipant\n \t *\n-\t * @param int|null $attendeeId\n-\t * @param string|null $participant\n-\t * @param string|null $sessionId\n+\t * @param int $attendeeId\n \t * @return DataResponse\n \t */\n-\tpublic function demoteModerator(?int $attendeeId, ?string $participant, ?string $sessionId): DataResponse {\n-\t\treturn $this->changeParticipantType($attendeeId, $participant, $sessionId, false);\n+\tpublic function demoteModerator(int $attendeeId): DataResponse {\n+\t\treturn $this->changeParticipantType($attendeeId, false);\n \t}\n \n \t/**\n \t * Toggle a user/guest to moderator/guest-moderator or vice-versa based on\n-\t * attendeeId (v3) or userId/sessionId (v1+v2)\n+\t * attendeeId\n \t *\n-\t * @param int|null $attendeeId\n-\t * @param string|null $userId\n-\t * @param string|null $sessionId\n+\t * @param int $attendeeId\n \t * @param bool $promote Shall the attendee be promoted or demoted\n \t * @return DataResponse\n \t */\n-\tprotected function changeParticipantType(?int $attendeeId, ?string $userId, ?string $sessionId, bool $promote): DataResponse {\n+\tprotected function changeParticipantType(int $attendeeId,  bool $promote): DataResponse {\n \t\ttry {\n-\t\t\tif ($attendeeId !== null) {\n-\t\t\t\t$targetParticipant = $this->room->getParticipantByAttendeeId($attendeeId);\n-\t\t\t} elseif ($userId !== null) {\n-\t\t\t\t$targetParticipant = $this->room->getParticipant($userId);\n-\t\t\t} else {\n-\t\t\t\t$targetParticipant = $this->room->getParticipantBySession($sessionId);\n-\t\t\t}\n+\t\t\t$targetParticipant = $this->room->getParticipantByAttendeeId($attendeeId);\n \t\t} catch (ParticipantNotFoundException $e) {\n \t\t\treturn new DataResponse([], Http::STATUS_NOT_FOUND);\n \t\t}\n@@ -1828,7 +1517,7 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\t}\n \t\t}\n \n-\t\treturn new DataResponse($this->formatRoomV2andV3($this->room, $this->participant));\n+\t\treturn new DataResponse($this->formatRoom($this->room, $this->participant));\n \t}\n \n \t/**\n@@ -1856,7 +1545,7 @@ class RoomController extends AEnvironmentAwareController {\n \t\t\treturn new DataResponse([], Http::STATUS_BAD_REQUEST);\n \t\t}\n \n-\t\treturn new DataResponse($this->formatRoomV2andV3($this->room, $this->participant));\n+\t\treturn new DataResponse($this->formatRoom($this->room, $this->participant));\n \t}\n \n \t/**\ndiff --git a/src/services/participantsService.js b/src/services/participantsService.js\nindex f480d75d4..e222cd2e3 100644\n--- a/src/services/participantsService.js\n+++ b/src/services/participantsService.js\n@@ -185,32 +185,6 @@ const removeCurrentUserFromConversation = async function(token) {\n \treturn response\n }\n \n-const removeUserFromConversation = async function(token, userId) {\n-\ttry {\n-\t\tconst response = await axios.delete(generateOcsUrl(\'apps/spreed/api/v4\', 2) + `room/${token}/participants`, {\n-\t\t\tparams: {\n-\t\t\t\tparticipant: userId,\n-\t\t\t},\n-\t\t})\n-\t\treturn response\n-\t} catch (error) {\n-\t\tconsole.debug(error)\n-\t}\n-}\n-\n-const removeGuestFromConversation = async function(token, sessionId) {\n-\ttry {\n-\t\tconst response = await axios.delete(generateOcsUrl(\'apps/spreed/api/v4\', 2) + `room/${token}/participants/guests`, {\n-\t\t\tparams: {\n-\t\t\t\tparticipant: sessionId,\n-\t\t\t},\n-\t\t})\n-\t\treturn response\n-\t} catch (error) {\n-\t\tconsole.debug(error)\n-\t}\n-}\n-\n const removeAttendeeFromConversation = async function(token, attendeeId) {\n \tconst response = await axios.delete(generateOcsUrl(\'apps/spreed/api/v4\', 2) + `room/${token}/attendees`, {\n \t\tparams: {\n@@ -267,8 +241,6 @@ export {\n \tleaveConversationSync,\n \taddParticipant,\n \tremoveCurrentUserFromConversation,\n-\tremoveUserFromConversation,\n-\tremoveGuestFromConversation,\n \tremoveAttendeeFromConversation,\n \tpromoteToModerator,\n \tdemoteFromModerator,\n', 'commit_subject': 'Remove old API version handlings', 'repo_name': 'nextcloud/spreed', 'committer': {'name': 'Joas Schilling', 'email': 'ab43a7c9cb5b2380afc4ddf8b3e2583169b39a02@schilljs.com', 'time_sec': '1615200981', 'tz_offset': '60', 'date': {'seconds': '1615200981'}}, 'commit_tree': '145ad09a7e365a346bbbd84e0af66b4202fac0da', 'commit_message': 'Remove old API version handlings\n\nSigned-off-by: Joas Schilling <ab43a7c9cb5b2380afc4ddf8b3e2583169b39a02@schilljs.com>\n', 'language_name': 'JavaScript', 'license': 'agpl-3.0', 'hunks': "@@ -185,32 +185,6 @@ const removeCurrentUserFromConversation = async function(token) {\n \treturn response\n }\n \n-const removeUserFromConversation = async function(token, userId) {\n-\ttry {\n-\t\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/participants`, {\n-\t\t\tparams: {\n-\t\t\t\tparticipant: userId,\n-\t\t\t},\n-\t\t})\n-\t\treturn response\n-\t} catch (error) {\n-\t\tconsole.debug(error)\n-\t}\n-}\n-\n-const removeGuestFromConversation = async function(token, sessionId) {\n-\ttry {\n-\t\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/participants/guests`, {\n-\t\t\tparams: {\n-\t\t\t\tparticipant: sessionId,\n-\t\t\t},\n-\t\t})\n-\t\treturn response\n-\t} catch (error) {\n-\t\tconsole.debug(error)\n-\t}\n-}\n-\n const removeAttendeeFromConversation = async function(token, attendeeId) {\n \tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/attendees`, {\n \t\tparams: {\n@@ -267,8 +241,6 @@ export {\n \tleaveConversationSync,\n \taddParticipant,\n \tremoveCurrentUserFromConversation,\n-\tremoveUserFromConversation,\n-\tremoveGuestFromConversation,\n \tremoveAttendeeFromConversation,\n \tpromoteToModerator,\n \tdemoteFromModerator,\n", 'addition_count': 0, 'deletion_count': 28, 'src_file': 'a/src/services/participantsService.js', 'tgt_file': 'b/src/services/participantsService.js', 'file_extension': '.js', 'before_file': "/**\n * @copyright Copyright (c) 2019 Marco Ambrosini <marcoambrosini@icloud.com>\n *\n * @author Marco Ambrosini <marcoambrosini@icloud.com>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\nimport axios from '@nextcloud/axios'\nimport {\n\tgenerateOcsUrl,\n} from '@nextcloud/router'\nimport {\n\tsignalingJoinConversation,\n\tsignalingLeaveConversation,\n} from '../utils/webrtc/index.js'\nimport { PARTICIPANT } from '../constants.js'\n\nconst PERMISSIONS = PARTICIPANT.PERMISSIONS\n\n/**\n * Joins the current user to a conversation specified with\n * the token.\n *\n * @param {object} data the wrapping object;\n * @param {string} data.token The conversation token;\n * @param {boolean} data.forceJoin whether to force join;\n * @param {options} options request options;\n */\nconst joinConversation = async ({ token, forceJoin = false }, options) => {\n\tconst response = await axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/active', { token }), {\n\t\tforce: forceJoin,\n\t}, options)\n\n\t// FIXME Signaling should not be synchronous\n\tawait signalingJoinConversation(token, response.data.ocs.data.sessionId)\n\n\treturn response\n}\n\n/**\n * Joins the current user to a conversation specified with\n * the token.\n *\n * @param {string} token The conversation token;\n */\nconst rejoinConversation = async (token) => {\n\treturn axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/active', { token }))\n}\n\n/**\n * Leaves the conversation specified with the token.\n *\n * @param {string} token The conversation token;\n */\nconst leaveConversation = async function(token) {\n\ttry {\n\t\t// FIXME Signaling should not be synchronous\n\t\tawait signalingLeaveConversation(token)\n\n\t\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/active', { token }))\n\t\treturn response\n\t} catch (error) {\n\t\tconsole.debug(error)\n\t\t// FIXME: should throw\n\t}\n}\n\n/**\n * Leaves the conversation specified with the token.\n *\n * @param {string} token The conversation token;\n */\nconst leaveConversationSync = function(token) {\n\taxios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/active', { token }))\n}\n\n/**\n * Add a participant to a conversation.\n *\n * @param {token} token the conversation token.\n * @param {string} newParticipant the id of the new participant\n * @param {string} source the source Source of the participant as returned by the autocomplete suggestion endpoint (default is users)\n */\nconst addParticipant = async function(token, newParticipant, source) {\n\tconst response = await axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants', { token }), {\n\t\tnewParticipant,\n\t\tsource,\n\t})\n\treturn response\n}\n\n/**\n * Removes the the current user from the conversation specified with the token.\n *\n * @param {string} token The conversation token;\n */\nconst removeCurrentUserFromConversation = async function(token) {\n\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/self', { token }))\n\treturn response\n}\n\nconst removeAttendeeFromConversation = async function(token, attendeeId) {\n\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/attendees', { token }), {\n\t\tparams: {\n\t\t\tattendeeId,\n\t\t},\n\t})\n\treturn response\n}\n\nconst promoteToModerator = async (token, options) => {\n\tconst response = await axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/moderators', { token }), options)\n\treturn response\n}\n\nconst demoteFromModerator = async (token, options) => {\n\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4/room/{token}/moderators', { token }), {\n\t\tparams: options,\n\t})\n\treturn response\n}\n\nconst fetchParticipants = async (token, options) => {\n\toptions = options || {}\n\toptions.params = options.params || {}\n\toptions.params.includeStatus = true\n\tconst response = await axios.get(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants', { token }), options)\n\treturn response\n}\n\nconst setGuestUserName = async (token, userName) => {\n\tconst response = await axios.post(generateOcsUrl('apps/spreed/api/v1/guest/{token}/name', { token }), {\n\t\tdisplayName: userName,\n\t})\n\treturn response\n}\n\n/**\n * Resends email invitations for the given conversation.\n * If no userId is set, send to all applicable participants.\n *\n * @param {string} token conversation token\n * @param {number} attendeeId attendee id to target, or null for all\n */\nconst resendInvitations = async (token, { attendeeId = null }) => {\n\tawait axios.post(generateOcsUrl('apps/spreed/api/v4/room/{token}/participants/resend-invitations', { token }), {\n\t\tattendeeId,\n\t})\n}\n\n/**\n * Sends call notification for the given attendee in the conversation.\n *\n * @param {string} token conversation token\n * @param {number} attendeeId attendee id to target\n */\nconst sendCallNotification = async (token, { attendeeId }) => {\n\tawait axios.post(generateOcsUrl('apps/spreed/api/v4/call/{token}/ring/{attendeeId}', { token, attendeeId }))\n}\n\n/**\n * Grants all permissions to an attendee in a given conversation\n *\n * @param {string} token conversation token\n * @param {number} attendeeId attendee id to target\n */\nconst grantAllPermissionsToParticipant = async (token, attendeeId) => {\n\tawait axios.put(generateOcsUrl('apps/spreed/api/v4/room/{token}/attendees/permissions', { token }), {\n\t\tattendeeId,\n\treturn response\n}\n\nconst removeUserFromConversation = async function(token, userId) {\n\ttry {\n\t\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/participants`, {\n\t\t\tparams: {\n\t\t\t\tparticipant: userId,\n\t\t\t},\n\t\t})\n\t\treturn response\n\t} catch (error) {\n\t\tconsole.debug(error)\n\t}\n}\n\nconst removeGuestFromConversation = async function(token, sessionId) {\n\ttry {\n\t\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/participants/guests`, {\n\t\t\tparams: {\n\t\t\t\tparticipant: sessionId,\n\t\t\t},\n\t\t})\n\t\treturn response\n\t} catch (error) {\n\t\tconsole.debug(error)\n\t}\n}\n\nconst removeAttendeeFromConversation = async function(token, attendeeId) {\n\tconst response = await axios.delete(generateOcsUrl('apps/spreed/api/v4', 2) + `room/${token}/attendees`, {\n\t\tparams: {\n * Removes all permissions to an attendee in a given conversation\n *\n * @param {string} token conversation token\n * @param {number} attendeeId attendee id to target\n */\nconst removeAllPermissionsFromParticipant = async (token, attendeeId) => {\n\tawait axios.put(generateOcsUrl('apps/spreed/api/v4/room/{token}/attendees/permissions', { token }), {\n\t\tattendeeId,\n\t\tmethod: 'set',\n\t\tpermissions: PERMISSIONS.CUSTOM,\n\t})\n}\n\n/**\n * Set permission for an attendee in a given conversation.\n *\n * @param {string} token conversation token\n * @param {number} attendeeId attendee id to target\n * @param {number} permission the type of permission to be granted. Valid values are\n * any sums of 'DEFAULT', 'CUSTOM', 'CALL_START', 'CALL_JOIN', 'LOBBY_IGNORE',\n * 'PUBLISH_AUDIO', 'PUBLISH_VIDEO', 'PUBLISH_SCREEN'.\n */\nconst setPermissions = async (token, attendeeId, permission) => {\n\tawait axios.put(generateOcsUrl('apps/spreed/api/v4/room/{token}/attendees/permissions', { token }),\n\t\t{\n\t\t\tattendeeId,\n\t\t\tmethod: 'set',\n\t\t\tpermissions: permission,\n\t\t})\n}\n\nexport {\n\tjoinConversation,\n\trejoinConversation,\n\tleaveConversation,\n\tleaveConversationSync,\n\taddParticipant,\n\tremoveCurrentUserFromConversation,\n\tremoveAttendeeFromConversation,\n\tpromoteToModerator,\n\tdemoteFromModerator,\n\tfetchParticipants,\n\tsetGuestUserName,\n\tresendInvitations,\n\tsendCallNotification,\n\tgrantAllPermissionsToParticipant,\n\tremoveAllPermissionsFromParticipant,\n\tsetPermissions,\n}\n\tleaveConversationSync,\n\taddParticipant,\n\tremoveCurrentUserFromConversation,\n\tremoveUserFromConversation,\n\tremoveGuestFromConversation,\n\tremoveAttendeeFromConversation,\n\tpromoteToModerator,\n\tdemoteFromModerator,\n", 'message': 'Remove old API version handlings\n\nSigned-off-by: Joas Schilling <ab43a7c9cb5b2380afc4ddf8b3e2583169b39a02@schilljs.com>\n', 'author_name': 'Joas Schilling', 'lang': 'js'}