mirror of https://github.com/kevachat/geminiapp
ghost
10 months ago
13 changed files with 466 additions and 1 deletions
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
/vendor/ |
||||
/host/*/ |
||||
|
||||
composer.lock |
@ -1,2 +1,30 @@
@@ -1,2 +1,30 @@
|
||||
# geminiapp |
||||
Application for Gemini Protocol |
||||
|
||||
KevaChat Application for Gemini Protocol |
||||
|
||||
## Roadmap |
||||
|
||||
* [x] Multiple host support |
||||
* [x] Room list |
||||
* [ ] Room threads |
||||
* [ ] Post publication |
||||
* [ ] Media viewer |
||||
* [ ] Users auth |
||||
* [x] Error handlers |
||||
|
||||
## Install |
||||
|
||||
`git clone https://github.com/kevachat/geminiapp.git` |
||||
`cd geminiapp` |
||||
`composer update` |
||||
|
||||
## Setup |
||||
|
||||
* `mkdir host/127.0.0.1` |
||||
* `cp example/config.json host/127.0.0.1/config.json` |
||||
* `cp host/127.0.0.1` |
||||
* `openssl req -x509 -newkey rsa:4096 -keyout key.rsa -out cert.pem -days 365 -nodes -subj "/CN=127.0.0.1"` |
||||
|
||||
## Start |
||||
|
||||
* `php src/server.php` |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
{ |
||||
"name": "kevachat/geminiapp", |
||||
"description": "KevaChat Application for Gemini Protocol", |
||||
"type": "project", |
||||
"license": "MIT", |
||||
"autoload": { |
||||
"psr-4": { |
||||
"Kevachat\\Geminiapp\\": "src/" |
||||
} |
||||
}, |
||||
"require": { |
||||
"yggverse/titan-ii": "^1.0", |
||||
"kevachat/kevacoin": "^1.6" |
||||
} |
||||
} |
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
{ |
||||
"kevacoin": |
||||
{ |
||||
"server": |
||||
{ |
||||
"protocol":"http", |
||||
"host":"127.0.0.1", |
||||
"port":9992, |
||||
"username":"", |
||||
"password":"" |
||||
} |
||||
}, |
||||
"gemini": |
||||
{ |
||||
"server": |
||||
{ |
||||
"host":"127.0.0.1", |
||||
"port":1965 |
||||
} |
||||
}, |
||||
"kevachat": |
||||
{ |
||||
"room": |
||||
{ |
||||
"pin":[] |
||||
}, |
||||
"post": |
||||
{ |
||||
"key": |
||||
{ |
||||
"regex":"/^([\\d]+)@([A-z0-9\\.\\:\\[\\]]+)$/" |
||||
}, |
||||
"value": |
||||
{ |
||||
"regex":"/.*/ui" |
||||
} |
||||
}, |
||||
"user": |
||||
{ |
||||
"name": |
||||
{ |
||||
"regex":"/^[0-9A-z-]{2,16}$/ui" |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
|
||||
_ __ ____ _ _ |
||||
| |/ /_____ ____ _ / ___| |__ __ _| |_ |
||||
| ' // _ \ \ / / _` | | | '_ \ / _` | __| |
||||
| . \ __/\ V / (_| | |___| | | | (_| | |_ |
||||
|_|\_\___| \_/ \__,_|\____|_| |_|\__,_|\__| |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
<?php |
||||
|
||||
namespace Kevachat\Geminiapp\Controller; |
||||
|
||||
class Error |
||||
{ |
||||
private $_config; |
||||
|
||||
public function __construct($config) |
||||
{ |
||||
$this->_config = $config; |
||||
} |
||||
|
||||
public function oops() |
||||
{ |
||||
return str_replace( |
||||
[ |
||||
'{logo}', |
||||
'{home}' |
||||
], |
||||
[ |
||||
file_get_contents( |
||||
__DIR__ . '/../../logo.ascii' |
||||
), |
||||
( |
||||
$this->_config->gemini->server->port == 1965 ? |
||||
sprintf( |
||||
'gemini://%s', |
||||
$this->_config->gemini->server->host |
||||
) : |
||||
sprintf( |
||||
'gemini://%s:%d', |
||||
$this->_config->gemini->server->host, |
||||
$this->_config->gemini->server->port |
||||
) |
||||
) |
||||
], |
||||
file_get_contents( |
||||
__DIR__ . '/../view/oops.gemini' |
||||
) |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,162 @@
@@ -0,0 +1,162 @@
|
||||
<?php |
||||
|
||||
namespace Kevachat\Geminiapp\Controller; |
||||
|
||||
class Room |
||||
{ |
||||
private $_config; |
||||
|
||||
private \Kevachat\Kevacoin\Client $_kevacoin; |
||||
|
||||
public function __construct($config) |
||||
{ |
||||
// Init config |
||||
$this->_config = $config; |
||||
|
||||
// Init kevacoin |
||||
$this->_kevacoin = new \Kevachat\Kevacoin\Client( |
||||
$this->_config->kevacoin->server->protocol, |
||||
$this->_config->kevacoin->server->host, |
||||
$this->_config->kevacoin->server->port, |
||||
$this->_config->kevacoin->server->username, |
||||
$this->_config->kevacoin->server->password |
||||
); |
||||
} |
||||
|
||||
public function list(): string |
||||
{ |
||||
// Get room list |
||||
$namespaces = []; |
||||
|
||||
foreach ((array) $this->_kevacoin->kevaListNamespaces() as $namespace) |
||||
{ |
||||
// Skip system namespaces |
||||
if (str_starts_with($namespace['displayName'], '_')) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
// Calculate room totals |
||||
$total = 0; |
||||
|
||||
foreach ((array) $this->_kevacoin->kevaFilter($namespace['namespaceId']) as $record) |
||||
{ |
||||
// Skip values with meta keys |
||||
if (str_starts_with($record['key'], '_')) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
// Validate value format allowed in settings |
||||
if (!preg_match((string) $this->_config->kevachat->post->value->regex, $record['value'])) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
// Validate key format allowed in settings |
||||
if (!preg_match($this->_config->kevachat->post->key->regex, $record['key'], $matches)) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
// Timestamp required in key |
||||
if (empty($matches[1])) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
// Username required in key |
||||
if (empty($matches[2])) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
// Legacy usernames backport (used to replace undefined names to @anon) |
||||
/* |
||||
if (!preg_match((string) $this->_config->kevachat->user->name->regex, $matches[2])) |
||||
{} |
||||
*/ |
||||
|
||||
$total++; |
||||
} |
||||
|
||||
// Add to room list |
||||
$namespaces[] = |
||||
[ |
||||
'namespace' => $namespace['namespaceId'], |
||||
'name' => $namespace['displayName'], |
||||
'total' => $total, |
||||
'pin' => in_array( |
||||
$namespace['namespaceId'], |
||||
$this->_config->kevachat->room->pin |
||||
) |
||||
]; |
||||
} |
||||
|
||||
// Sort rooms by total |
||||
array_multisort( |
||||
array_column( |
||||
$namespaces, |
||||
'total' |
||||
), |
||||
SORT_DESC, |
||||
$namespaces |
||||
); |
||||
|
||||
// Build rooms view |
||||
$view = file_get_contents( |
||||
__DIR__ . '/../view/room.gemini' |
||||
); |
||||
|
||||
$rooms = []; |
||||
foreach ($namespaces as $namespace) |
||||
{ |
||||
$rooms[] = str_replace( |
||||
[ |
||||
'{name}', |
||||
'{total}', |
||||
'{link}' |
||||
], |
||||
[ |
||||
$namespace['name'], |
||||
$namespace['total'], |
||||
( |
||||
$this->_config->gemini->server->port == 1965 ? |
||||
sprintf( |
||||
'gemini://%s/room/%s', |
||||
$this->_config->gemini->server->host, |
||||
$namespace['namespace'] |
||||
) : |
||||
sprintf( |
||||
'gemini://%s:%d/%s', |
||||
$this->_config->gemini->server->host, |
||||
$this->_config->gemini->server->port, |
||||
$namespace['namespace'] |
||||
) |
||||
) |
||||
], |
||||
$view |
||||
); |
||||
} |
||||
|
||||
// Build final view and send to response |
||||
return str_replace( |
||||
[ |
||||
'{logo}', |
||||
'{rooms}' |
||||
], |
||||
[ |
||||
file_get_contents( |
||||
__DIR__ . '/../../logo.ascii' |
||||
), |
||||
implode( |
||||
PHP_EOL, |
||||
$rooms |
||||
) |
||||
], |
||||
file_get_contents( |
||||
__DIR__ . '/../view/rooms.gemini' |
||||
) |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
<?php |
||||
|
||||
// Load dependencies |
||||
require_once __DIR__ . '/../vendor/autoload.php'; |
||||
|
||||
// Scan host |
||||
foreach ((array) scandir(__DIR__ . '/../host') as $host) |
||||
{ |
||||
// Skip meta |
||||
if ($host == '.' || $host == '..' || is_file($host)) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
// Check host configured |
||||
if (!file_exists(__DIR__ . '/../host/' . $host . '/config.json')) |
||||
{ |
||||
echo sprintf( |
||||
_('Host "%s" not configured!') . PHP_EOL, |
||||
$host |
||||
); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
// Check cert exists |
||||
if (!file_exists(__DIR__ . '/../host/' . $host . '/cert.pem')) |
||||
{ |
||||
echo sprintf( |
||||
_('Certificate for host "%s" not found!') . PHP_EOL, |
||||
$host |
||||
); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
// Check key exists |
||||
if (!file_exists(__DIR__ . '/../host/' . $host . '/key.rsa')) |
||||
{ |
||||
echo sprintf( |
||||
_('Key for host "%s" not found!') . PHP_EOL, |
||||
$host |
||||
); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
// Init config |
||||
$config = json_decode( |
||||
file_get_contents( |
||||
__DIR__ . '/../host/' . $host . '/config.json' |
||||
) |
||||
); |
||||
|
||||
// Init server |
||||
$server = new \Yggverse\TitanII\Server(); |
||||
|
||||
$server->setCert( |
||||
__DIR__ . '/../host/' . $host . '/cert.pem' |
||||
); |
||||
|
||||
$server->setKey( |
||||
__DIR__ . '/../host/' . $host . '/key.rsa' |
||||
); |
||||
|
||||
$server->setHandler( |
||||
function (\Yggverse\TitanII\Request $request): \Yggverse\TitanII\Response |
||||
{ |
||||
global $config; |
||||
|
||||
$response = new \Yggverse\TitanII\Response(); |
||||
|
||||
$response->setCode( |
||||
20 |
||||
); |
||||
|
||||
$response->setMeta( |
||||
'text/gemini' |
||||
); |
||||
|
||||
// Route begin |
||||
switch ($request->getPath()) |
||||
{ |
||||
// Home page |
||||
case '/': |
||||
|
||||
// Get rooms list |
||||
include_once __DIR__ . '/controller/room.php'; |
||||
|
||||
$room = new \Kevachat\Geminiapp\Controller\Room( |
||||
$config |
||||
); |
||||
|
||||
$response->setContent( |
||||
$room->list() |
||||
); |
||||
|
||||
return $response; |
||||
|
||||
// Dynamical requests |
||||
default: |
||||
|
||||
if (str_starts_with($request->getPath(), '/room/')) |
||||
{ |
||||
// @TODO |
||||
} |
||||
} |
||||
|
||||
// Set default response |
||||
include_once __DIR__ . '/controller/error.php'; |
||||
|
||||
$error = new \Kevachat\Geminiapp\Controller\Error( |
||||
$config |
||||
); |
||||
|
||||
$response->setContent( |
||||
$error->oops() |
||||
); |
||||
|
||||
return $response; |
||||
} |
||||
); |
||||
|
||||
// Start server |
||||
echo sprintf( |
||||
_('Start server "%s" started on %s:%d') . PHP_EOL, |
||||
$host, |
||||
$config->gemini->server->host, |
||||
$config->gemini->server->port |
||||
); |
||||
|
||||
$server->start( |
||||
$config->gemini->server->host, |
||||
$config->gemini->server->port |
||||
); |
||||
} |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
```{logo}``` |
||||
|
||||
# Oops! |
||||
|
||||
This page could not be opened |
||||
|
||||
Just try from |
||||
|
||||
=> {home} |
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
## {author} |
||||
|
||||
> {quote} |
||||
|
||||
``` {time} |
||||
|
||||
{message} |
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
## {name} ({total}) |
||||
|
||||
=> {link} |
Loading…
Reference in new issue