You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sessioncommunities.online/php/utils/servers-rooms.php

424 lines
9.9 KiB
PHTML

<?php
include_once "$PROJECT_ROOT/languages/language_flags.php";
class CommunityRoom implements JsonSerializable {
public readonly object $server;
public readonly int $active_users;
public readonly int $active_users_cutoff;
public readonly string $token;
public readonly string $name;
public readonly array $admins;
public readonly array $moderators;
public readonly float $created;
public readonly string $description;
public readonly ?int $image_id;
public readonly int $info_updates;
public readonly int $message_sequence;
public readonly bool $read;
public readonly bool $upload;
public readonly bool $write;
// Custom properties
public readonly string $language_flag;
private function __construct($server, array $data) {
global $languages;
$this->server = $server;
$this->active_users = $data['active_users'];
$this->active_users_cutoff = $data['active_users_cutoff'];
$this->name = $data['name'];
$this->token = $data['token'];
$this->admins = $data['admins'];
$this->moderators = $data['moderators'];
$this->created = $data['created'];
$this->description = $data['description'] ?? "";
$this->image_id = $data['image_id'];
$this->info_updates = $data['info_updates'];
$this->message_sequence = $data['message_sequence'];
$this->read = $data['read'];
$this->write = $data['write'];
$this->upload = $data['upload'];
$room_identifier = $this->get_room_identifier();
$this->language_flag =
isset($languages[$room_identifier])
? $languages[$room_identifier]
: "";
}
/**
* Create a CommunityRoom instance from data.
* @param CommunityServer $server
*/
public static function from_data($server, array $data) {
return new CommunityRoom($server, $data);
}
/**
* Create an array of CommunityRoom instances from data.
* @param array[] $data
* @return CommunityRoom[]
*/
public static function from_data_array($server, array $data) {
return array_map(function($room_data) use ($server) {
return CommunityRoom::from_data($server, $room_data);
}, $data);
}
function jsonSerialize(): array {
$data = get_object_vars($this);
unset($data['server']);
return $data;
}
/**
* Returns array of staff Session IDs.
* @return string[]
*/
function get_staff() {
return array_unique(
[...$this->admins, ...$this->moderators]
);
}
/**
* Returns seconds elapsed since room was created.
*/
function get_age(): float {
return time() - $this->created;
}
function get_preview_url(): string {
$base_url = $this->server->base_url;
$token = $this->token;
return "$base_url/r/$token";
}
function get_invite_url(): string {
$base_url = $this->server->base_url;
$token = $this->token;
return "$base_url/r/$token/invite.png";
}
function get_join_url(): string {
$base_url = $this->server->base_url;
$pubkey = $this->server->pubkey;
$token = $this->token;
return "$base_url/$token?public_key=$pubkey";
}
function get_icon_url(): string | bool {
$image_id = $this->image_id;
if ($image_id == null)
return false;
$base_url = $this->server->base_url;
$token = $this->token;
return "$base_url/room/$token/file/$image_id";
}
/**
* Returns our format of room identifier,
* i.e. token+pubkey[:4]
*/
function get_room_identifier(): string {
$token = $this->token;
$pubkey_4 = substr($this->server->pubkey, 0, 4);
return "$token+$pubkey_4";
}
}
$SERVER_STRINGIFY_MODE = 0;
class CommunityServer implements JsonSerializable {
// public static int $STRINGIFY_MODE = 0;
public string $base_url = "";
public string $pubkey = "";
public ?array $rooms = null;
/**
* Instructs CommunityServer instances
* to stringify to their base URL.
*/
private static function next_dedupe_by_url() {
global $SERVER_STRINGIFY_MODE;
$SERVER_STRINGIFY_MODE = 0;
}
/**
* Instructs CommunityServer instances
* to stringify to their public key.
*/
private static function next_dedupe_by_pubkey() {
global $SERVER_STRINGIFY_MODE;
$SERVER_STRINGIFY_MODE = 1;
}
/**
* Filters the given servers to remove URL duplicates.
* @param CommunityServer[] $servers
* @return CommunityServer[]
*/
public static function dedupe_by_url($servers) {
CommunityServer::next_dedupe_by_url();
return array_unique($servers);
}
/**
* Filters the given servers to remove pubkey duplicates.
* @param CommunityServer[] $servers
* @return CommunityServer[]
*/
public static function dedupe_by_pubkey($servers) {
CommunityServer::next_dedupe_by_pubkey();
return array_unique($servers);
}
private function __construct() { }
/**
* Compare two CommunityServer instances.
* @param CommunityServer $a
* @param CommunityServer $b
*/
static function compare($a, $b): int {
return strcmp($a->base_url, $b->base_url);
}
/**
* Sort an array of servers in-place based on URL.
* @param CommunityServer[] &$servers
*/
static function sort(array &$servers) {
usort($servers, 'CommunityServer::compare');
}
/**
* Stringify CommunityServer by custom property
* to allow de-duping using array_uniq.
*/
function __toString(): string {
global $SERVER_STRINGIFY_MODE;
return match($SERVER_STRINGIFY_MODE) {
0 => $this->base_url,
1 => $this->pubkey
};
}
function jsonSerialize(): array {
return get_object_vars($this);
}
/**
* @return CommunityServer
*/
static function from_host($host) {
$server = new CommunityServer();
$server->base_url = $host;
return $server;
}
/**
* @return CommunityServer
*/
static function from_known_host($host, $pubkey) {
$server = new CommunityServer();
$server->base_url = $host;
$server->pubkey = $pubkey;
return $server;
}
/**
* @return CommunityServer[]
*/
static function from_known_hosts($hosts, $pubkeys) {
$servers = [];
foreach ($hosts as $base_url) {
$server = new CommunityServer();
$server->base_url = $base_url;
$hostname = url_get_base($base_url, false);
$server->pubkey = $pubkeys[$hostname];
$servers[] = $server;
}
return $servers;
}
/**
* @return CommunityServer
*/
static function from_join_url(string $join_url) {
$server = new CommunityServer();
$server->base_url = url_get_base($join_url);
$server->set_pubkey_from_url($join_url);
return $server;
}
/**
* @return CommunityServer[]
*/
static function from_join_urls(array $join_urls) {
return array_map(
'CommunityServer::from_join_url',
$join_urls
);
}
/**
* @param array $data
* @return CommunityServer
*/
static function from_data(array $data) {
$server = new CommunityServer();
$server->base_url = $data['base_url'];
$server->pubkey = $data['pubkey'];
$server->rooms = CommunityRoom::from_data_array($server, $data['rooms']);
return $server;
}
/**
* @param array[] $data
* @return CommunityServer[]
*/
static function from_data_array(array $data) {
return array_map(
'CommunityServer::from_data',
$data
);
}
/**
* @param CommunityServer[] $servers
* @return CommunityRoom[]
*/
static function enumerate_rooms($servers) {
$rooms = [];
foreach ($servers as $server) {
$rooms[] = $server->rooms;
}
return array_merge([], ...$rooms);
}
/**
* Polls all servers for rooms.
* @param CommunityServer[] $servers Servers to poll.
* @return CommunityServer[] Reachable servers.
*/
public static function poll_reachable(array $servers): array {
$reachable_servers = [];
// Synchronous for-loop for now.
foreach ($servers as $server) {
if (!($server->fetch_rooms())) continue;
// Accept failures to fetch pubkey if already known.
// (Has happened.)
if (!(
$server->fetch_pubkey()
|| $server->has_pubkey()
)) continue;
$reachable_servers[] = $server;
}
return $reachable_servers;
}
function set_pubkey($pubkey) {
if ($this->has_pubkey() && $this->pubkey != $pubkey) {
$base_url = $this->base_url;
throw new ValueError("Pubkey mismatch for $base_url");
}
$this->pubkey = $pubkey;
}
function set_pubkey_from_url($join_url) {
$url_components = parse_url($join_url);
parse_str($url_components['query'], $query_components);
$this->pubkey = $query_components['public_key'];
}
function has_pubkey() {
return $this->pubkey != "";
}
function is_reachable() {
return url_is_reachable($this->base_url);
}
/**
* Attempt to fetch rooms for self using SOGS API.
*
* @return bool True if successful, false otherwise.
*/
function fetch_rooms() {
$base_url = $this->base_url;
log_info("Fetching rooms for $base_url.");
$rooms = curl_get_contents("$base_url/rooms?all=1");
if (!$rooms) {
log_warning("Could not fetch rooms for $base_url.");
return false;
}
$room_data = json_decode($rooms, true);
if ($room_data == null) {
log_warning("Could not parse rooms for $base_url.");
return false;
}
$this->rooms = CommunityRoom::from_data_array($this, $room_data);
return true;
}
/**
* Attempt to fetch own public key by parsing SOGS HTML preview.
*
* @return bool True if successful, false otherwise.
*/
function fetch_pubkey() {
if (empty($this->rooms)) {
log_error("Server has no rooms to poll for public key");
throw new Error("Server has no rooms to poll for public key");
}
$preview_url = $this->rooms[0]->get_preview_url();
log_info("Fetching pubkey from $preview_url");
$room_view = curl_get_contents($preview_url);
if (!$room_view) {
log_warning("Failed to fetch room preview from $preview_url.");
return false;
}
$links = parse_join_links($room_view);
if (!isset($links[0])) {
log_warning("Could not locate join link in preview at $preview_url.");
return false;
}
$this->set_pubkey_from_url($links[0]);
return true;
}
}
?>