diff --git a/.gitignore b/.gitignore index d0f43f4..fb466b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +#! +#! Do not forget to add items you do not want cleaned +#! to .gitpreserve as well! +#! + # Fetched room info output/servers.json output/tags.json diff --git a/custom/config/known-servers.ini.sample b/custom/config/known-servers.ini.sample new file mode 100644 index 0000000..ce229e2 --- /dev/null +++ b/custom/config/known-servers.ini.sample @@ -0,0 +1,3 @@ +[https://open.getsession.org] +pubkey=a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238 +icon=session diff --git a/custom/config/room-overrides.ini.sample b/custom/config/room-overrides.ini.sample index c9e721d..74e00b7 100644 --- a/custom/config/room-overrides.ini.sample +++ b/custom/config/room-overrides.ini.sample @@ -1,2 +1,22 @@ -[room+1234] +; server-wide overrides + +[66666666] +rating=nsfw + +; room overrides + +[room+12345678] staff_count=4 +safety=nsfw + +[room+23456789] +stickied=true + +[room+3456789a] +feedback_room=true + +[test+12345678] +testing=true + +[bad+66666666] +rating=unsafe diff --git a/etc/.gitpreserve b/etc/.gitpreserve index 0a62ab7..bbb09ea 100644 --- a/etc/.gitpreserve +++ b/etc/.gitpreserve @@ -12,3 +12,5 @@ cache-lt # Custom Doxyfile configuration Doxyfile +# Custom content +custom/** diff --git a/php/fetch-servers.php b/php/fetch-servers.php index aba8104..2eadc03 100644 --- a/php/fetch-servers.php +++ b/php/fetch-servers.php @@ -9,6 +9,7 @@ require_once 'getenv.php'; require_once 'utils/getopt.php'; require_once 'utils/utils.php'; + require_once 'utils/read-config.php'; require_once 'servers/known-servers.php'; require_once 'servers/servers-rooms.php'; require_once 'servers/sources.php'; @@ -27,11 +28,13 @@ */ function main() { global $PROJECT_ROOT, $CACHE_ROOT, $ROOMS_FILE, $TAGS_FILE, - $KNOWN_SERVERS, $KNOWN_PUBKEYS, $DO_DRY_RUN, $DO_ARCHIVE_FILES; + $DO_DRY_RUN, $DO_ARCHIVE_FILES; // Create default directories.. file_exists($CACHE_ROOT) or mkdir($CACHE_ROOT, 0700); + $local_config = LocalConfig::get_instance(); + // Query our sources and store the resulting HTML. $sources = new CommunitySources(); @@ -41,7 +44,10 @@ $servers = CommunityServer::from_join_urls($sources->get_join_urls()); // Add known hosts. - $servers = [...CommunityServer::from_known_hosts($KNOWN_SERVERS, $KNOWN_PUBKEYS), ...$servers]; + $servers = [ + ...CommunityServer::from_known_hosts($local_config->get_known_servers()), + ...$servers + ]; // Merge servers with the same URL. $servers = CommunityServer::dedupe_by_url($servers); diff --git a/php/servers/known-servers.php b/php/servers/known-servers.php index 646dd4e..c9f89e8 100644 --- a/php/servers/known-servers.php +++ b/php/servers/known-servers.php @@ -4,71 +4,6 @@ * Provide hardcoded information on Community servers and Communities. */ - /** - * @var $KNOWN_SERVERS - * Hardcoded Community server URLs. - */ - $KNOWN_SERVERS = array( - // Official server - - "https://open.getsession.org", - - // Found with shodan.io: - - "http://164.92.176.135", - "http://176.119.147.102", - "http://60a9fc9.online-server.cloud", - "http://93.95.230.10", - "http://94.176.239.60", - "http://captain.geekgalaxy.com", - "http://session.hwreload.it", - "http://sogs.k9net.org", - "https://gaohuangse.work", - - // Found via Reddit: - - "http://5.39.117.98", - - // Found on Session: - // "http://88.212.53.198:4080", - - // legacy - // "http://3.69.29.128", // former https://reccacon.com - - // Offline: - // "http://13.233.251.36:8081", - - // Removed by request: - // "http://116.203.51.179", - // "http://bitcoincash.tokyo/", - - // Removed out of decency: - // [redacted] - ); - - /** - * @var $KNOWN_PUBKEYS - * Associative array of SOGS public keys by server hostname. - * Keys are sorted alphabetically. - */ - $KNOWN_PUBKEYS = array( - "116.203.51.179" => "39016f991400c35a46e11e06cb2a64d6d8ab6652e484a556b14f7cf57ed7e73a", - "13.233.251.36:8081" => "efcaecf00aebf5b75e62cf1fd550c6052842e1415a9339406e256c8b27cd2039", - "164.92.176.135" => "e529311ec8fb6fdb950aaa4fb71fc4da3ea59c6c9ba2886708b9538eea6aa213", - "176.119.147.102" => "e093994156ec92e4c13d0387208bfa48ae56dd88b8f60a03980d9ef048af1e3f", - "3.69.29.128" => "02bdb3f74b59355724b1a59676127729602b5e34261efb965a94ccac94cd6a62", - "5.39.117.98" => "4bec6d6c7b502a819b47b3af75272c0774ab1214fba33fb5ec29949f864eb028", - "60a9fc9.online-server.cloud" => "7908bcd748313355f99e62f9c1f11c395d04019410edb7ee1618dbe26a423c4f", - "88.212.53.198:4080" => "7c5908efd053e7d4634f606328d0a3e50b86e4bef5f76ab84658435321110600", - "93.95.230.10" => "b501f2dc7dc912aa0981b0ba10f2ba739d2f729a7d9b37022aee505aaf72807c", - "94.176.239.60" => "2cbde327e9da216af9a69876bc57e16cc0c540b0aa2dfecdd1c115e67993b040", - "captain.geekgalaxy.com" => "7242ad657dc2dd20e902a6fa82c34465907b67e80daf50173f38d5745abbaa24", - "gaohuangse.work" => "2cd535b2e6a1fb40d1166910561d85a027782b1d751118897eca6056f4738a10", - "open.getsession.org" => "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238", - "session.hwreload.it" => "4b3e75eedd2116b4dab0bcb6443b0e9fbfce7bcf1d35970bdad8a57a0113fb20", - "sogs.k9net.org" => "fdcb047eb78520e925fda512a45ae74c6e2de9e0df206b3c0471bf1509919559", - ); - /** * @var string[] $ICON_ALLOWLIST * Hostnames considered to have safe room icons. diff --git a/php/servers/servers-rooms.php b/php/servers/servers-rooms.php index fe02f7b..a153742 100644 --- a/php/servers/servers-rooms.php +++ b/php/servers/servers-rooms.php @@ -405,11 +405,9 @@ * @return int */ function get_staff_count(): int { - $room_id = $this->get_room_identifier(); - return ( LocalConfig::get_instance() - ->get_room_staff_count_override($room_id) + ->get_room_staff_count_override($this) ?? count($this->get_staff()) ); } @@ -912,7 +910,7 @@ * when the servers are in correct order, interchangeable, or in reverse order, * respectively. */ - static function compare_by_url($a, $b): int { + static function compare_by_url(CommunityServer $a, CommunityServer $b): int { return strcmp( $a->get_hostname(), $b->get_hostname() @@ -1125,27 +1123,25 @@ /** * 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. + * @param string[] $hosts Array from server base URLs to array including pubkey. * @return CommunityServer[] Array of resulting Community servers. */ - static function from_known_hosts(array $hosts, array $pubkeys) { + static function from_known_hosts(array $known_servers) { $servers = []; - foreach ($hosts as $base_url) { + foreach ($known_servers as $base_url => $server_details) { $server = new CommunityServer(); $server->base_url = $base_url; $hostname = url_get_base($base_url, false); - if (!isset($pubkeys[$hostname])) { + if (!isset($server_details['pubkey'])) { log_error("Known server $hostname has no known pubkey."); throw new Error("Known server $hostname has no known pubkey"); } - $server->set_pubkey($pubkeys[$hostname]); + $server->set_pubkey($server_details['pubkey']); $servers[] = $server; } @@ -1698,10 +1694,10 @@ * Checks whether this server belongs to Session / Oxen Privacy Tech Foundation. */ function is_official_server() { - global $KNOWN_PUBKEYS; + $config = LocalConfig::get_instance(); return ( $this->base_url == "https://open.getsession.org" && - $this->get_pubkey() == $KNOWN_PUBKEYS['open.getsession.org'] + $this->get_pubkey() == $config->get_known_servers()['https://open.getsession.org']['pubkey'] ); } } diff --git a/php/utils/read-config.php b/php/utils/read-config.php index dc4374b..e6b2e9f 100644 --- a/php/utils/read-config.php +++ b/php/utils/read-config.php @@ -6,11 +6,40 @@ require_once 'php/utils/logging.php'; * @var string $CONFIG_ROOT */ +function config_requiring_type( + mixed $value, + bool $int = false, + bool $string = false, + bool $bool = false, + bool $float = false, + bool $null = true +) { + if (is_int($value) && $int) return $value; + if (is_bool($value) && $bool) return $value; + if (is_string($value) && $string) return $value; + if (is_float($value) && $float) return $value; + if (is_null($value) && $null) return $value; + $realtype = get_debug_type($value); + throw new Error("Unexpected configuration option type: $realtype"); +} + +/** + * Provides custom configuration values. + */ class LocalConfig { private function __construct() { - $this->room_overrides = + $room_overrides = LocalConfig::maybe_parse_ini_file(LocalConfig::ROOM_OVERRIDES_CONFIG) ?? array(); + // Sort room overrides last + uksort($room_overrides, function($identifier_a, $identifier_b) { + return str_contains($identifier_b, "+") - str_contains($identifier_a, "+"); + }); + $this->room_overrides = $room_overrides; + $this->room_overrides_computed = array(); + $this->known_servers = + LocalConfig::maybe_parse_ini_file(LocalConfig::KNOWN_SERVERS_CONFIG) + ?? array(); } private static LocalConfig | null $instance = null; @@ -24,9 +53,14 @@ class LocalConfig { } private const ROOM_OVERRIDES_CONFIG = "room-overrides.ini"; + private const KNOWN_SERVERS_CONFIG = "known-servers.ini"; private readonly array $room_overrides; + private array $room_overrides_computed; + + private readonly array $known_servers; + private static function maybe_parse_ini_file(string $filename): array | null { global $CONFIG_ROOT; $file = "$CONFIG_ROOT/$filename"; @@ -36,7 +70,7 @@ class LocalConfig { return null; } - return parse_ini_file($file, process_sections: true, scanner_mode: INI_SCANNER_RAW); + return parse_ini_file($file, process_sections: true, scanner_mode: INI_SCANNER_TYPED); } /** @@ -46,13 +80,92 @@ class LocalConfig { return new LocalConfig(); } - private function get_room_override(string $room_id, string $override_key) { - $room_overrides = $this->room_overrides[$room_id] ?? array(); - return $room_overrides[$override_key] ?? null; + private function get_room_overrides(CommunityRoom $room) { + $room_id = $room->get_room_identifier(); + + if (isset($this->room_overrides_computed[$room_id])) { + return $this->room_overrides_computed[$room_id]; + } + + $room_overrides = array(); + + foreach ($this->room_overrides as $identifier => $overrides) { + if ($room->matched_by_identifier($identifier)) { + $room_overrides = [...$room_overrides, ...$overrides]; + } + } + + return $this->room_overrides_computed[$room_id] = $room_overrides; + } + + public function get_room_staff_count_override(CommunityRoom $room): int | null { + return config_requiring_type( + $this->get_room_overrides($room)['staff_count'], + int: true, + ); + } + + public function get_room_safety_override(CommunityRoom $room): RoomSafety { + return RoomSafety::from_keyword( + $this->get_room_overrides($room)['safety'] + ); + } + + private function get_bool_override_value(CommunityRoom $room, string $override_key) { + return config_requiring_type( + $this->get_room_overrides($room)[$override_key] === true, + bool: true + ) === true; + } + + public function is_testing_room(CommunityRoom $room): bool { + return $this->get_bool_override_value($room, 'testing'); + } + + public function is_stickied_room(CommunityRoom $room): bool { + return $this->get_bool_override_value($room, 'stickied'); + } + + public function get_known_servers(): array { + return $this->known_servers; + } +} + +class RoomSafety { + private function __construct(int $value) { + $this->safety = $value; + } + + private readonly int $safety; + + private const UNSAFE = -2; + private const NOT_SAFE_FOR_WORK = -1; + private const UNSET = 0; + private const SAFE_FOR_WORK = 1; + + public static function from_keyword(string|null $keyword) { + if (null == $keyword) { + return new RoomSafety(RoomSafety::UNSET); + } + switch (mb_strtolower($keyword)) { + case "unsafe": + return new RoomSafety(RoomSafety::UNSAFE); + case "nsfw": + return new RoomSafety(RoomSafety::NOT_SAFE_FOR_WORK); + case "sfw": + return new RoomSafety(RoomSafety::SAFE_FOR_WORK); + default: + throw new Error("Unknown safety class: $keyword"); + } + } + + private function is_set() { + return $this->safety !== RoomSafety::UNSET; } - public function get_room_staff_count_override(string $room_id) { - return $this->get_room_override($room_id, 'staff_count'); + public function rated_nsfw(): bool | null { + if (!$this->is_set()) return null; + return $this->safety <= RoomSafety::NOT_SAFE_FOR_WORK; } }