Browse Source

draft room threads feature

main
ghost 10 months ago
parent
commit
e4804170f0
  1. 2
      README.md
  2. 4
      example/config.json
  3. 248
      src/controller/room.php
  4. 20
      src/server.php
  5. 7
      src/view/post.gemini
  6. 5
      src/view/posts.gemini

2
README.md

@ -14,7 +14,7 @@ KevaChat Application for Gemini Protocol @@ -14,7 +14,7 @@ KevaChat Application for Gemini Protocol
* [x] Multiple host support
* [x] Room list
* [ ] Room threads
* [x] Room threads
* [ ] Post publication
* [ ] Media viewer
* [ ] Users auth

4
example/config.json

@ -22,6 +22,10 @@ @@ -22,6 +22,10 @@
{
"room":
{
"key":
{
"regex":"/^[\\w\\s\\._-]{2,64}$/ui"
},
"pin":[]
},
"post":

248
src/controller/room.php

@ -36,48 +36,22 @@ class Room @@ -36,48 +36,22 @@ class Room
continue;
}
// Validate room name compatible with settings
if (!preg_match((string) $this->_config->kevachat->room->key->regex, $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]))
// Is protocol compatible post
if ($this->post($namespace['namespaceId'], $record['key'], 'txid'))
{
continue;
$total++;
}
// 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
@ -159,4 +133,208 @@ class Room @@ -159,4 +133,208 @@ class Room
)
);
}
public function posts(string $namespace): ?string
{
$posts = [];
foreach ((array) $this->_kevacoin->kevaFilter($namespace) as $record)
{
if (empty($record['key']))
{
continue;
}
if ($post = $this->post($namespace, $record['key']))
{
$posts[] = $post;
}
}
return str_replace(
[
'{logo}',
'{subject}',
'{posts}'
],
[
file_get_contents(
__DIR__ . '/../../logo.ascii'
),
$namespace,
implode(
PHP_EOL,
$posts
)
],
file_get_contents(
__DIR__ . '/../view/posts.gemini'
)
);
}
public function post(string $namespace, string $key, ?string $field = null): ?string
{
// Check record exists
if (!$record = (array) $this->_kevacoin->kevaGet($namespace, $key))
{
return null;
}
// Skip values with meta keys
if (str_starts_with($record['key'], '_'))
{
return null;
}
// Validate value format allowed in settings
if (!preg_match((string) $this->_config->kevachat->post->value->regex, $record['value']))
{
return null;
}
// Validate key format allowed in settings
if (!preg_match($this->_config->kevachat->post->key->regex, $record['key'], $matches))
{
return null;
}
// Timestamp required in key
if (empty($matches[1]))
{
return null;
}
// Username required in key
if (empty($matches[2]))
{
return null;
}
// Is raw field request
if ($field && isset($record[$field]))
{
return $record[$field];
}
// Legacy usernames backport
if (!preg_match((string) $this->_config->kevachat->user->name->regex, $matches[2]))
{
$matches[2] = '@anon';
}
// Try to find related quote value
$quote = null;
if (preg_match('/^@([A-z0-9]{64})/', $record['value'], $mention))
{
if (!empty($mention[1]))
{
$quote = '@' . $mention[1]; // @TODO replace to post message by txid
// Remove mention from message
$record['value'] = preg_replace('/^@([A-z0-9]{64})/', null, $record['value']);
}
}
// Build final view and send to response
return str_replace(
[
'{time}',
'{author}',
'{quote}',
'{message}',
],
[
$this->_ago(
$matches[1]
),
'@' . $matches[2],
trim(
$quote
),
trim(
$record['value']
)
],
file_get_contents(
__DIR__ . '/../view/post.gemini'
)
);
}
private function _ago(int $time): string
{
$diff = time() - $time;
if ($diff < 1)
{
return _('now');
}
$values =
[
365 * 24 * 60 * 60 =>
[
_('year ago'),
_('years ago'),
_(' years ago')
],
30 * 24 * 60 * 60 =>
[
_('month ago'),
_('months ago'),
_(' months ago')
],
24 * 60 * 60 =>
[
_('day ago'),
_('days ago'),
_(' days ago')
],
60 * 60 =>
[
_('hour ago'),
_('hours ago'),
_(' hours ago')
],
60 =>
[
_('minute ago'),
_('minutes ago'),
_(' minutes ago')
],
1 =>
[
_('second ago'),
_('seconds ago'),
_(' seconds ago')
]
];
foreach ($values as $key => $value)
{
$result = $diff / $key;
if ($result >= 1)
{
$round = round($result);
return sprintf(
'%s %s',
$round,
$this->_plural(
$round,
$value
)
);
}
}
}
private function _plural(int $number, array $texts)
{
$cases = [2, 0, 1, 1, 1, 2];
return $texts[(($number % 100) > 4 && ($number % 100) < 20) ? 2 : $cases[min($number % 10, 5)]];
}
}

20
src/server.php

@ -100,9 +100,25 @@ foreach ((array) scandir(__DIR__ . '/../host') as $host) @@ -100,9 +100,25 @@ foreach ((array) scandir(__DIR__ . '/../host') as $host)
// Dynamical requests
default:
if (str_starts_with($request->getPath(), '/room/'))
// Room posts by namespace
if (preg_match('/^\/room\/(N[A-z0-9]{33})$/', $request->getPath(), $matches))
{
// @TODO
if (!empty($matches[1]))
{
include_once __DIR__ . '/controller/room.php';
$room = new \Kevachat\Geminiapp\Controller\Room(
$config
);
$response->setContent(
$room->posts(
$matches[1]
)
);
return $response;
}
}
}

7
src/view/post.gemini

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
## {author}
> {quote}
``` {time}
{message}
```
{time}
```

5
src/view/posts.gemini

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
```{logo}```
# {subject}
{posts}
Loading…
Cancel
Save