@ -2,30 +2,75 @@
include_once "$PROJECT_ROOT/languages/language_flags.php";
include_once "$PROJECT_ROOT/languages/language_flags.php";
/**
* Representation of Session Community room.
*/
class CommunityRoom implements JsonSerializable {
class CommunityRoom implements JsonSerializable {
/** @var CommunityServer $server */
/** @var CommunityServer $server Server this room belongs to. */
public readonly object $server;
public readonly object $server;
/** @var ?int $active_users Number of active users in the defined period. */
public readonly ?int $active_users;
public readonly ?int $active_users;
/** @var ?int $active_users_cutoff Period for `$active_users`, in seconds. */
public readonly ?int $active_users_cutoff;
public readonly ?int $active_users_cutoff;
/** @var string $token Room name in Community API. */
public readonly string $token;
public readonly string $token;
/** @var ?string $name User-facing name given to Community. */
public readonly ?string $name;
public readonly ?string $name;
/** @var ?string[] $admins The mixed Session IDs of public room admins. */
public readonly ?array $admins;
public readonly ?array $admins;
/** @var ?string[] $moderators The mixed Session IDs of public room moderators. */
public readonly ?array $moderators;
public readonly ?array $moderators;
/** @var ?float $created UNIX timestamp of room creation, in seconds. */
public readonly ?float $created;
public readonly ?float $created;
/** @var ?string $description User-facing description given to Community. */
public readonly ?string $description;
public readonly ?string $description;
/** @var ?int $image_id Optional file ID for this room's icon, as served under the room files. */
public readonly ?int $image_id;
public readonly ?int $image_id;
/** @var ?int $info_updates Monotonic integer counter that increases
* whenever the room's metadata changes. */
public readonly ?int $info_updates;
public readonly ?int $info_updates;
/** @var ?int $message_sequence Monotonic room post counter that
* increases each time a message is posted, edited, or deleted in this room. */
public readonly ?int $message_sequence;
public readonly ?int $message_sequence;
/**
* @var ?bool $read
* This boolean flag indicates whether a regular user
* has permission to read messages in this room.
*/
public readonly ?bool $read;
public readonly ?bool $read;
/**
* @var ?bool $upload
* This boolean flag indicates whether a regular user
* has permission to upload files to this room.
*/
public readonly ?bool $upload;
public readonly ?bool $upload;
/**
* @var ?bool $write
* This boolean flag indicates whether a regular user
* has permission to write messages to this room.
*/
public readonly ?bool $write;
public readonly ?bool $write;
/** @var string[] $tags */
public readonly array $tags;
// Custom properties
// Custom properties
public readonly string $language_flag;
private function __construct($server, array $details) {
/**
* @var string $language_flag
* Optional Unicode emoji of region matching
* the primary language of this room.
*
* Custom attribute.
*/
public readonly ?string $language_flag;
/**
* @var string[] $tags
* String tags applied to room by creator or submitter.
*
* Custom attribute.
*/
public readonly array $tags;
private function __construct(\CommunityServer $server, array $details) {
global $languages;
global $languages;
$this->server = $server;
$this->server = $server;
@ -58,7 +103,7 @@
}
}
/**
/**
* Return all room data to be serialized to JSON
* Return information for JSON serialization.
*/
*/
function jsonSerialize(): array {
function jsonSerialize(): array {
$details = get_object_vars($this);
$details = get_object_vars($this);
@ -67,7 +112,7 @@
}
}
/**
/**
* Create a CommunityRoom instance from data.
* Create a CommunityRoom instance from loaded data.
* @param CommunityServer $server
* @param CommunityServer $server
*/
*/
public static function from_details($server, array $details) {
public static function from_details($server, array $details) {
@ -75,7 +120,7 @@
}
}
/**
/**
* Create an array of CommunityRoom instances from data.
* Create an array of CommunityRoom instances from loaded data.
* @param array[] $details
* @param array[] $details
* @return CommunityRoom[]
* @return CommunityRoom[]
*/
*/
@ -102,18 +147,27 @@
return time() - $this->created;
return time() - $this->created;
}
}
/**
* Return the browser preview URL for this room.
*/
function get_preview_url(): string {
function get_preview_url(): string {
$base_url = $this->server->base_url;
$base_url = $this->server->base_url;
$token = $this->token;
$token = $this->token;
return "$base_url/r/$token";
return "$base_url/r/$token";
}
}
/**
* Return the QR code invite URL for this room.
*/
function get_invite_url(): string {
function get_invite_url(): string {
$base_url = $this->server->base_url;
$base_url = $this->server->base_url;
$token = $this->token;
$token = $this->token;
return "$base_url/r/$token/invite.png";
return "$base_url/r/$token/invite.png";
}
}
/**
* Return the in-app join URL for this room.
*/
function get_join_url(): string {
function get_join_url(): string {
$base_url = $this->server->base_url;
$base_url = $this->server->base_url;
$pubkey = $this->server->pubkey;
$pubkey = $this->server->pubkey;
@ -121,6 +175,9 @@
return "$base_url/$token?public_key=$pubkey";
return "$base_url/$token?public_key=$pubkey";
}
}
/**
* Return the URL of this room's designated icon.
*/
function get_icon_url(): string | bool {
function get_icon_url(): string | bool {
$image_id = $this->image_id;
$image_id = $this->image_id;
@ -134,8 +191,8 @@
}
}
/**
/**
* Returns our format of room identifier,
* Return our format of room identifier.
* i.e. token+pubkey[:4]
* @return string String in the form `token+pubkey[:4]`.
*/
*/
function get_room_identifier(): string {
function get_room_identifier(): string {
$token = $this->token;
$token = $this->token;
@ -144,13 +201,15 @@
}
}
}
}
$SERVER_STRINGIFY_MODE = 0;
/**
* Class representing Session Community server hosting Community rooms.
*/
class CommunityServer implements JsonSerializable {
class CommunityServer implements JsonSerializable {
// public static int $STRINGIFY_MODE = 0;
/** @var string $base_url The root URL of this server. */
public string $base_url = "";
public string $base_url = "";
/** @var string $pubkey The SOGS protocol pubkey of this server. */
public string $pubkey = "";
public string $pubkey = "";
/** @var ?\CommunityRoom[] Array of rooms hosted by this server. */
public ?array $rooms = null;
public ?array $rooms = null;
/**
/**
* @var string[] $room_hints
* @var string[] $room_hints
@ -162,9 +221,12 @@
private function __construct() {}
private function __construct() {}
/**
/**
* Compare two CommunityServer instances by URL.
* Compare two CommunityServer instances by base URL.
* @param CommunityServer $a
* @param CommunityServer $a First server to compare URLs.
* @param CommunityServer $b
* @param CommunityServer $b Second server to compare URLs.
* @return int A number less than, equal to, or greater than zero
* when the servers are in correct order, interchangable, or in reverse order,
* respectively.
*/
*/
static function compare_by_url($a, $b): int {
static function compare_by_url($a, $b): int {
return strcmp(
return strcmp(
@ -174,7 +236,7 @@
}
}
/**
/**
* Sort an array of servers in- place based on URL.
* Sort an array of servers in place based on URL.
* @param CommunityServer[] & $servers
* @param CommunityServer[] & $servers
*/
*/
static function sort_by_url(array & $servers) {
static function sort_by_url(array & $servers) {
@ -183,16 +245,18 @@
/**
/**
* Compare two CommunityServer instances by public key.
* Compare two CommunityServer instances by public key.
* @param CommunityServer $a
* @param CommunityServer $a First server to compare public keys.
* @param CommunityServer $b
* @param CommunityServer $b Second server to compare public keys.
* @return int A number less than, equal to, or greater than zero
* when the servers are in correct order, interchangable, or in reverse order,
* respectively.
*/
*/
static function compare_by_pubkey($a, $b): int {
static function compare_by_pubkey($a, $b): int {
return strcmp($a->pubkey, $b->pubkey);
return strcmp($a->pubkey, $b->pubkey);
}
}
/**
/**
* Sorts an array of servers in- place by public key.
* Sorts an array of servers in place by public key.
* @param CommunityServer[] $servers
* @param CommunityServer[] $servers
*/
*/
public static function sort_by_pubkey(& $servers) {
public static function sort_by_pubkey(& $servers) {
@ -200,7 +264,7 @@
}
}
/**
/**
* Absorbs extra info from another instance describing the same server.
* Absorbs extra info from another instance of the same server.
* @param CommunityServer $server
* @param CommunityServer $server
*/
*/
private function merge_from($server) {
private function merge_from($server) {
@ -235,9 +299,9 @@
}
}
/**
/**
* Merges consecutive servers on equality of given attribute.
* Merges consecutive servers in array in place on equality of given attribute.
* @param CommunityServer[] $servers Servers sorted by attribute-
* @param CommunityServer[] $servers Servers sorted by given attribute.
* @param string $key Method call whose result to merge servers by .
* @param string $method Method name to retrieve attribute from server .
*/
*/
private static function merge_by(& $servers, string $method) {
private static function merge_by(& $servers, string $method) {
// Backwards-merging to preserve indexing for unprocessed servers.
// Backwards-merging to preserve indexing for unprocessed servers.
@ -251,6 +315,9 @@
}
}
}
}
/**
* Write details about this server to debug log.
*/
private function log_details() {
private function log_details() {
$base_url = $this->base_url;
$base_url = $this->base_url;
$count_rooms = count($this->rooms ?? []);
$count_rooms = count($this->rooms ?? []);
@ -261,8 +328,8 @@
/**
/**
* Filters the given servers to remove URL duplicates.
* Filters the given servers to remove URL duplicates.
* @param CommunityServer[] $servers
* @param CommunityServer[] $servers Servers to merge by URL.
* @return CommunityServer[]
* @return CommunityServer[] Merged URL-unique servers.
*/
*/
public static function dedupe_by_url($servers) {
public static function dedupe_by_url($servers) {
CommunityServer::sort_by_url($servers);
CommunityServer::sort_by_url($servers);
@ -276,8 +343,8 @@
/**
/**
* Filters the given servers to remove pubkey duplicates.
* Filters the given servers to remove pubkey duplicates.
* @param CommunityServer[] $servers
* @param CommunityServer[] $servers Servers to merge by public key.
* @return CommunityServer[]
* @return CommunityServer[] Merged pubkey-unique servers.
*/
*/
public static function dedupe_by_pubkey($servers) {
public static function dedupe_by_pubkey($servers) {
CommunityServer::sort_by_pubkey($servers);
CommunityServer::sort_by_pubkey($servers);
@ -289,14 +356,21 @@
return $servers;
return $servers;
}
}
/**
* Return information for JSON serialization.
*/
function jsonSerialize(): array {
function jsonSerialize(): array {
return get_object_vars($this);
return get_object_vars($this);
}
}
/**
/**
* @return CommunityServer[]
* Create server instances located on hardcoded hosts.
* @param string[] $hosts Array of base URLs for known servers.
* @param string[] $pubkeys
* Associative array from hostnames to SOGS public keys.
* @return CommunityServer[] Array of resulting Community servers.
*/
*/
static function from_known_hosts($hosts, $pubkeys) {
static function from_known_hosts(array $hosts, array $pubkeys) {
$servers = [];
$servers = [];
foreach ($hosts as $base_url) {
foreach ($hosts as $base_url) {
@ -315,7 +389,10 @@
}
}
/**
/**
* @return CommunityServer[]
* Create server instances from given room join URLs.
* Resulting servers will know of the embedded room tokens.
* @param string[] $join_urls Join URLs found in the wild.
* @return CommunityServer[] Array of resulting Community servers.
*/
*/
static function from_join_urls(array $join_urls) {
static function from_join_urls(array $join_urls) {
$servers = [];
$servers = [];
@ -332,8 +409,9 @@
}
}
/**
/**
* @param array $details
* Create Community server instance from loaded server data.
* @return CommunityServer
* @param array $details Decoded JSON associative data about server.
* @return CommunityServer Server represented by given data.
*/
*/
static function from_details(array $details) {
static function from_details(array $details) {
$server = new CommunityServer();
$server = new CommunityServer();
@ -346,8 +424,9 @@
}
}
/**
/**
* @param array[] $details
Create Community server instance from array loaded server data.
* @return CommunityServer[]
* @param array $details Decoded JSON associative arrays about server.
* @return CommunityServer[] Servers represented by given data.
*/
*/
static function from_details_array(array $details_array) {
static function from_details_array(array $details_array) {
$servers = [];
$servers = [];
@ -360,8 +439,10 @@
}
}
/**
/**
* @param CommunityServer[] $servers
* Collect the rooms among the given Community servers.
* @param CommunityServer[] $servers Array of Community servers.
* @return CommunityRoom[]
* @return CommunityRoom[]
* Array of all rooms contained in the given servers.
*/
*/
static function enumerate_rooms($servers) {
static function enumerate_rooms($servers) {
$rooms = [];
$rooms = [];
@ -372,9 +453,11 @@
}
}
/**
/**
* Polls all servers for rooms.
* Polls given servers for rooms and public key and saves this info.
* @param CommunityServer[] $servers Servers to poll.
* Servers will be disqualified if no rooms can be found,
* @return CommunityServer[] Reachable servers.
* and/or if no public key is obtained or hardcoded.
* @param CommunityServer[] $servers Servers to fetch.
* @return CommunityServer[] Servers polled successfully.
*/
*/
public static function poll_reachable(array $servers): array {
public static function poll_reachable(array $servers): array {
$reachable_servers = [];
$reachable_servers = [];
@ -390,25 +473,44 @@
return $reachable_servers;
return $reachable_servers;
}
}
/**
* Returns the URL scheme of this server.
* @return string "http" or "https".
*/
function get_scheme() {
function get_scheme() {
return parse_url($this->base_url, PHP_URL_SCHEME);
return parse_url($this->base_url, PHP_URL_SCHEME);
}
}
/**
* Reduces this server's base URL to HTTP.
*/
function downgrade_scheme() {
function downgrade_scheme() {
$base_url = $this->base_url;
$base_url = $this->base_url;
$this->base_url = "http://" . $this->get_hostname();
$this->base_url = "http://" . $this->get_hostname();
log_info("Downgrading $base_url to HTTP.");
log_info("Downgrading $base_url to HTTP.");
}
}
/**
* Returns the hostname for this server.
* @return string URL with hostname and port, if applicable.
* Scheme not included.
*/
function get_hostname() {
function get_hostname() {
return url_get_base($this->base_url, include_scheme: false);
return url_get_base($this->base_url, include_scheme: false);
}
}
/**
* Returns the server's root URL.
* @return string URL with scheme, hostname, and port, if applicable.
*/
function get_base_url() {
function get_base_url() {
return $this->base_url;
return $this->base_url;
}
}
/**
* Returns the server's public key.
* @return string SOGS pubkey as used in the Session protocol.
*/
function get_pubkey() {
function get_pubkey() {
return $this->pubkey;
return $this->pubkey;
}
}
@ -437,11 +539,20 @@
$this->room_hints[] = url_get_token($join_url);
$this->room_hints[] = url_get_token($join_url);
}
}
function has_pubkey() {
/**
* Checks whether the current server SOGS public key is initialized.
* @return bool False if the public key is empty, true otherwise.
*/
function has_pubkey(): bool {
return $this->pubkey != "";
return $this->pubkey != "";
}
}
private function fetch_room_list() {
/**
* Attempts to fetch the current server's room listing.
* Downgrades the server's scheme to HTTP if necessary.
* @return array|false Associative data about rooms if successful.
*/
private function fetch_room_list(): array|bool {
$base_url = $this->base_url;
$base_url = $this->base_url;
list($rooms, $downgrade) = curl_get_contents_downgrade("$base_url/rooms?all=1");
list($rooms, $downgrade) = curl_get_contents_downgrade("$base_url/rooms?all=1");
if (!$rooms) {
if (!$rooms) {
@ -458,7 +569,12 @@
return $room_data;
return $room_data;
}
}
private function fetch_room_hints() {
/**
* Attempts to fetch the current server's rooms using observed room names.
* Downgrades the server's scheme to HTTP if necessary.
* @return ?array Associative data about rooms if successful.
*/
private function fetch_room_hints(): ?array {
$base_url = $this->base_url;
$base_url = $this->base_url;
$rooms = [];
$rooms = [];
@ -494,11 +610,11 @@
}
}
/**
/**
* Attempt to fetch rooms for self using SOGS API.
* Attempt to fetch rooms for tbe current server using SOGS API.
*
*
* @return bool True if successful, false otherwise.
* @return bool True if successful, false otherwise.
*/
*/
function fetch_rooms() {
function fetch_rooms(): bool {
$this->log_details();
$this->log_details();
$base_url = $this->base_url;
$base_url = $this->base_url;
@ -508,7 +624,7 @@
log_value($this->room_hints);
log_value($this->room_hints);
if (!url_is_reachable($base_url)) {
if (!url_is_reachable($base_url)) {
log_warning("Reachability test failed by $base_url.");
log_warning("Reachability test failed by $base_url.");
return [] ;
return false ;
}
}
}
}
@ -524,7 +640,11 @@
return true;
return true;
}
}
function fetch_or_has_pubkey() {
/**
* Fetch the server's SOGS public key if absent.
* @return bool True if pubkey is present or has been fetched, false otherwise.
*/
function fetch_or_has_pubkey(): bool {
$base_url = $this->base_url;
$base_url = $this->base_url;
// Do not use 'or' here; I learned the hard way.
// Do not use 'or' here; I learned the hard way.
$result = $this->fetch_pubkey() || $this->has_pubkey();
$result = $this->fetch_pubkey() || $this->has_pubkey();