mirror of
https://github.com/kevachat/geminiapp.git
synced 2025-03-10 04:21:02 +00:00
initial features draft
This commit is contained in:
parent
36b531afb1
commit
4da086c523
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/vendor/
|
||||||
|
/host/*/
|
||||||
|
|
||||||
|
composer.lock
|
30
README.md
30
README.md
@ -1,2 +1,30 @@
|
|||||||
# geminiapp
|
# 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`
|
15
composer.json
Normal file
15
composer.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
46
example/config.json
Normal file
46
example/config.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
host/.gitignore
vendored
Normal file
1
host/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/*/
|
6
logo.ascii
Normal file
6
logo.ascii
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
_ __ ____ _ _
|
||||||
|
| |/ /_____ ____ _ / ___| |__ __ _| |_
|
||||||
|
| ' // _ \ \ / / _` | | | '_ \ / _` | __|
|
||||||
|
| . \ __/\ V / (_| | |___| | | | (_| | |_
|
||||||
|
|_|\_\___| \_/ \__,_|\____|_| |_|\__,_|\__|
|
43
src/controller/error.php
Normal file
43
src/controller/error.php
Normal file
@ -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'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
162
src/controller/room.php
Normal file
162
src/controller/room.php
Normal file
@ -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'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
136
src/server.php
Normal file
136
src/server.php
Normal file
@ -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
|
||||||
|
);
|
||||||
|
}
|
9
src/view/oops.gemini
Normal file
9
src/view/oops.gemini
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
```{logo}```
|
||||||
|
|
||||||
|
# Oops!
|
||||||
|
|
||||||
|
This page could not be opened
|
||||||
|
|
||||||
|
Just try from
|
||||||
|
|
||||||
|
=> {home}
|
7
src/view/post.gemini
Normal file
7
src/view/post.gemini
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
## {author}
|
||||||
|
|
||||||
|
> {quote}
|
||||||
|
|
||||||
|
``` {time}
|
||||||
|
|
||||||
|
{message}
|
3
src/view/room.gemini
Normal file
3
src/view/room.gemini
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## {name} ({total})
|
||||||
|
|
||||||
|
=> {link}
|
5
src/view/rooms.gemini
Normal file
5
src/view/rooms.gemini
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
```{logo}```
|
||||||
|
|
||||||
|
# Rooms
|
||||||
|
|
||||||
|
{rooms}
|
Loading…
x
Reference in New Issue
Block a user