<!DOCTYPE html>
<html>
	<head>
		<meta charset='utf-8'>
		<meta name='author' content='Anton Yaky'>
		<meta name='viewport' content='width=device-width, initial-scale=1'>
		<title>Synapse Room Cleaner</title>
		<script>
			window.onload = (event) => {
				_el('homeserver').value = localStorage.getItem('homeserver');
				_el('access_token').value = localStorage.getItem('access_token');
			}
			function _el(id) {
				return document.getElementById(id);
			}
			function _val(id) {
				return document.getElementById(id).value;
			}
			function credentialsSave() {
				let homeserver = _val('homeserver');
				localStorage.setItem('homeserver', homeserver);
				let access_token = _val('access_token');
				localStorage.setItem('access_token', access_token);
				alert('Credentials saved to local storage. They will be automatically set next time this page is loaded.');
			}
			function credentialsClear() {
				_el('homeserver').value = '';
				_el('access_token').value = '';
				localStorage.removeItem('homeserver');
				localStorage.removeItem('access_token');
				alert('Credentials cleared.');
			}
			function roomsList(rooms_from) {
				const homeserver = _val('homeserver');
				const access_token = _val('access_token');
				if (!homeserver) {
					alert('Homeserver URL is required.');
					return;
				}
				if (!access_token) {
					alert('Access token is required.');
					return;
				}
				let url = `${homeserver}/_synapse/admin/v1/rooms?from=${rooms_from}&limit=10`;
				const search_term = _val('search_term');
				if (search_term) {
					url += `&search_term=${search_term}`
				}
				fetch(url, {
					method: 'GET',
					headers: { 'Authorization': `Bearer ${access_token}` }
				})
				.then((response) => {
					if (!response.ok)
						throw new Error(`${response.status} ${response.statusText}`);
					return response.json();
				})
				.then((data) => {
					_el('rooms_total').innerText = `Total rooms: ${data.total_rooms}`;
					_el('rooms_prev').style.display = "inline-block";
					_el('rooms_next').style.display = "inline-block";
					if (data.prev_batch != undefined) {
						_el('rooms_prev').setAttribute('onclick', `roomsList(${data.prev_batch})`)
						_el('rooms_prev').disabled = false;
					} else {
						_el('rooms_prev').disabled = true;
					}
					if (data.next_batch != undefined) {
						_el('rooms_next').setAttribute('onclick', `roomsList(${data.next_batch})`)
						_el('rooms_next').disabled = false;
					} else {
						_el('rooms_next').disabled = true;
					}
					let tableHtml = 
`				<thead>
					<tr>
						<th>ID</th>
						<th>Name</th>
						<th class='centered'>Encrypted</th>
						<th>Creator</th>
						<th class='centered'>Total Member Count</th>
						<th class='centered'>Local Member Count</th>
						<th>Local Member List</th>
						<th class='centered'></th>
					</tr>
				</thead>
				<tbody>`;
					for (let i = 0; i < data.rooms.length; i++) {
						const room = data.rooms[i]; 
						tableHtml +=
`					<tr>
						<td>${room.room_id}</td>
						<td>${room.name ? room.name : ''}</td>
						<td>${room.encryption ? 'Y' : 'N'}</td>
						<td>${room.creator}</td>
						<td class='centered'>${room.joined_members}</td>
						<td class='centered'>${room.joined_local_members}</td>
						<td id='room_members_${i}'><button onclick='roomGetMembers(${i},"${room.room_id}")'>Get Members</button></td>
						<td id='room_delete_state_${i}' class='centered'><button id='room_delete_${i}' onclick='roomDelete(${i},"${room.room_id}",false)' class='red'>Delete</buton></td>
				</tr>`;
					};
					tableHtml +=
`				</tbody>`
					_el('rooms_table').innerHTML = tableHtml;
				})
				.catch((errorText) => {
					alert(`Error getting a list of rooms: ${errorText}`);
				});
			}
			function roomGetMembers(i, room_id) {
				const homeserver = _val('homeserver');
				const access_token = _val('access_token');
				fetch(`${homeserver}/_synapse/admin/v1/rooms/${room_id}/members`, {
					method: 'GET',
					headers: { 'Authorization': `Bearer ${access_token}` }
				})
				.then((response) => {
					if (!response.ok)
						throw new Error(`${response.status} ${response.statusText}`);
					return response.json();
				})
				.then((data) => {
					if (data.total > 0) {
						let membersHtml = '<ul>';
						for (member of data.members) {
							membersHtml += `<li>${member}</li>`;
						}
						membersHtml += '</ul>';
						_el(`room_members_${i}`).innerHTML = membersHtml;
					} else {
						_el(`room_members_${i}`).innerText = 'Room is empty';
					}
				})
				.catch((errorText) => {
					alert(`Error getting a list of members for room ${room_id}: ${errorText}`);
				});
			}
			function roomDelete(i, room_id, force) {
				const homeserver = _val('homeserver');
				const access_token = _val('access_token');
				_el(`room_delete_state_${i}`).innerText = 'Deleting...';
				fetch(`${homeserver}/_synapse/admin/v1/rooms/${room_id}`, {
					method: 'DELETE',
					headers: {
						'Authorization': `Bearer ${access_token}`,
						'Content-Type': 'application/json'
					},
					body: JSON.stringify({
						'purge': true,
						'force_purge': (force === true)
					})
				})
				.then((response) => {
					if (!response.ok)
						throw new Error(`${response.status} ${response.statusText}`);
					_el(`room_delete_state_${i}`).innerText = 'Deleted';
					alert(`Room ${room_id} successfully deleted.`);
				})
				.catch((errorText) => {
					if (force) {
						alert(`Error deleting room ${room_id}: ${errorText}.`);
						_el(`room_delete_state_${i}`).innerText = 'Error';
					} else {
						alert(`Error deleting room ${room_id}: ${errorText}. You can try to force purge the room.`);
						_el(`room_delete_state_${i}`).innerHtml = `<button id='room_delete_${i}' onclick='roomDelete(${i},"${room.room_id}",true)' class='red'>Force Purge</buton>`;
					}
				});
			}
		</script>
		<style>
			:root {
				--light: rgb(255, 255, 255);
				--medium: rgb(165, 165, 165);
				--dark: rgb(28, 28, 28);
				--red: rgb(213, 25, 40);
				--blue: rgb(5, 100, 220);
			}
			html {
				font-family: Verdana,sans-serif;
				background-color: var(--light);
				color: var(--dark);
				font-size: 15px;
				font-family: "Inter","Arial","Helvetica",sans-serif;
			}
			body {
				margin: 8px;
				padding: 0;
			}
			h1 {
				font-size: 24px;
				font-weight: 600;
				margin: 4px 0;
			}
			h2 {
				font-size: 15px;
				font-weight: 600;
				margin: 24px 0 4px 0;
			}
			.text {
				min-height: 40px;
				margin: 2px 0;
			}
			a, a:visited {
				color: var(--blue);
				text-decoration: none;
				margin-right: 16px;
			}
			a:after {
				content: " ⭧";
			}
			label {
				display: inline-block;
				min-width: 180px;
				margin: 4px 0;
			}
			input {
				min-width: 150px;
				padding: 8px 9px;
				border: 1px solid var(--medium);
				border-radius: 8px;
				margin: 2px 0;
				font-size: 15px;
				font-family: "Inter","Arial","Helvetica",sans-serif;
			}
			input:focus {
				border: 1px solid var(--dark);
			}
			button {
				background-color: var(--dark);
				color: var(--light);
				font-size: 15px;
				font-weight: 600;
				font-family: "Inter","Arial","Helvetica",sans-serif;
				border: 0;
				/*border: 1px solid rgb(27, 29, 34);*/
				border-radius: 24px;
				padding: 7px 18px;
				min-width: 80px;
				margin: 4px 2px 4px 0;
			}
			button:disabled {
				background-color: var(--medium);
			}
			.red {
				background-color: var(--red);
			}
			.table-div {
				width: calc(100vw - 16px);
				border-radius: 8px;
				overflow-x: scroll;
			}
			table {
				border-collapse: collapse;
			}
			tr {
				height: 48px;
			}
			thead tr, tr:nth-child(even) {
				background-color: rgb(240, 240, 240);
			}
			thead th {
				padding: 4px;
				text-align: left;
			}
			td {
				padding: 4px;
				white-space: nowrap;
			}
			.centered {
				text-align: center;
			}
			ul {
				list-style: none;
				margin: 0;
				padding: 0;
			}
			main {
				margin-bottom: 36px;
			}
			footer {
				position: fixed;
				left: 0;
				bottom: 0;
				height: 20px;
				margin: 0;
				padding: 8px;
				width: 100%;
				background-color: var(--light);
			}
		</style>
	</head>
	<body>
		<header>
			<h1>Synapse Room Cleaner</h1>
		</header>
		<main>
			<h2>Credentials</h2>
			<div class='text'>
				<div>
					<a href='https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/index.html#authenticate-as-a-server-admin' target='_blank'>How to get an access token</a>
				</div>
				<div>
					Credentials can be saved to your browser's local storage.
				</div>
			</div>
			<div>
				<label for='homeserver'>Homeserver URL</label>
				<input id='homeserver' type='text' value=''>
			</div>
			<div>
				<label for='access_token'>Access token</label>
				<input id='access_token' type='password' value=''>
			</div>
			<div>
				<button onclick='credentialsSave()'>Save</button>
				<button onclick='credentialsClear()' class='red'>Clear</button>
			</div>
			<h2>Rooms</h2>
			<div>
				<label for='search_term'>Search by name (optional)</label>
				<input id='search_term' type='text'>
			</div>
			<div>
				<button onclick='roomsList(0)'>List Rooms</button>
				<span id='rooms_total'></span>
			</div>
			<div>
			</div>
			<div class='table-div'>
				<table id='rooms_table'></table>
			</div>
			<div>
				<button id='rooms_prev' onclick='roomsList(0)' disabled='true' style='display: none;'>Prev</button> <button id='rooms_next' onclick='roomsList(0)' disabled='true' style='display: none;'>Next</button>
			</div>
		</main>
		<footer>
			<div>
				(C) 2024 MIT <a href='https://yaky.dev' target='_blank'>Anton Yaky</a>
				<a href='https://github.com/yaky-dev/synapse-room-cleaner' target='_blank'>Source code on GitHub</a>
			</div>
		</footer>
	</body>
</html>