ghost
8 months ago
3 changed files with 569 additions and 0 deletions
@ -0,0 +1,57 @@ |
|||||||
|
{ |
||||||
|
"geminiapp": |
||||||
|
{ |
||||||
|
"server": |
||||||
|
{ |
||||||
|
"host":"127.0.0.1", |
||||||
|
"port":1965 |
||||||
|
}, |
||||||
|
"pagination": |
||||||
|
{ |
||||||
|
"limit":10 |
||||||
|
}, |
||||||
|
"links": |
||||||
|
[ |
||||||
|
"https://github.com/kvazar-network/geminiapp GitHub" |
||||||
|
], |
||||||
|
"string": |
||||||
|
{ |
||||||
|
"title":"Kvazar", |
||||||
|
"description":"Observe Kevacoin Universe", |
||||||
|
"navigation":"Navigation", |
||||||
|
"main":"Main", |
||||||
|
"latest":"Latest", |
||||||
|
"search":"Search", |
||||||
|
"part":"Part", |
||||||
|
"namespace":"Namespace", |
||||||
|
"key":"Key", |
||||||
|
"value":"Value", |
||||||
|
"results":"Results", |
||||||
|
"older":"Older", |
||||||
|
"newer":"Newer", |
||||||
|
"nothing":"Nothing" |
||||||
|
} |
||||||
|
}, |
||||||
|
"manticore": |
||||||
|
{ |
||||||
|
"server": |
||||||
|
{ |
||||||
|
"host":"127.0.0.1", |
||||||
|
"port":9308 |
||||||
|
}, |
||||||
|
"index": |
||||||
|
{ |
||||||
|
"document":{ |
||||||
|
"name":"kvazar", |
||||||
|
"meta": |
||||||
|
{ |
||||||
|
"morphology":"stem_cz,stem_enru", |
||||||
|
"index_exact_words":1, |
||||||
|
"html_strip":1, |
||||||
|
"min_word_len":3, |
||||||
|
"min_prefix_len":3 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,510 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
// Load dependencies |
||||||
|
require_once __DIR__ . '/../vendor/autoload.php'; |
||||||
|
|
||||||
|
// Check arguments |
||||||
|
if (empty($argv[1])) |
||||||
|
{ |
||||||
|
exit(_('Configured hostname required as argument!') . PHP_EOL); |
||||||
|
} |
||||||
|
|
||||||
|
// Check cert exists |
||||||
|
if (!file_exists(__DIR__ . '/../host/' . $argv[1] . '/cert.pem')) |
||||||
|
{ |
||||||
|
exit( |
||||||
|
sprintf( |
||||||
|
_('Certificate for host "%s" not found!') . PHP_EOL, |
||||||
|
$argv[1] |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Check key exists |
||||||
|
if (!file_exists(__DIR__ . '/../host/' . $argv[1] . '/key.rsa')) |
||||||
|
{ |
||||||
|
exit( |
||||||
|
sprintf( |
||||||
|
_('Key for host "%s" not found!') . PHP_EOL, |
||||||
|
$argv[1] |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Check host configured |
||||||
|
if (!file_exists(__DIR__ . '/../host/' . $argv[1] . '/config.json')) |
||||||
|
{ |
||||||
|
exit( |
||||||
|
sprintf( |
||||||
|
_('Host "%s" not configured!') . PHP_EOL, |
||||||
|
$argv[1] |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Init config |
||||||
|
$config = json_decode( |
||||||
|
file_get_contents( |
||||||
|
__DIR__ . '/../host/' . $argv[1] . '/config.json' |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
// Init index |
||||||
|
$index = new \Kvazar\Index\Manticore( |
||||||
|
$config->manticore->index->document->name, |
||||||
|
(array) $config->manticore->index->document->meta, |
||||||
|
$config->manticore->server->host, |
||||||
|
$config->manticore->server->port |
||||||
|
); |
||||||
|
|
||||||
|
// Init server |
||||||
|
$server = new \Yggverse\TitanII\Server(); |
||||||
|
|
||||||
|
$server->setCert( |
||||||
|
__DIR__ . '/../host/' . $argv[1] . '/cert.pem' |
||||||
|
); |
||||||
|
|
||||||
|
$server->setKey( |
||||||
|
__DIR__ . '/../host/' . $argv[1] . '/key.rsa' |
||||||
|
); |
||||||
|
|
||||||
|
$server->setHandler( |
||||||
|
function (\Yggverse\TitanII\Request $request): \Yggverse\TitanII\Response |
||||||
|
{ |
||||||
|
global $config; |
||||||
|
global $index; |
||||||
|
|
||||||
|
$response = new \Yggverse\TitanII\Response(); |
||||||
|
|
||||||
|
$response->setCode( |
||||||
|
20 |
||||||
|
); |
||||||
|
|
||||||
|
$response->setMeta( |
||||||
|
'text/gemini; charset=utf-8' |
||||||
|
); |
||||||
|
|
||||||
|
// Route begin |
||||||
|
switch (true) |
||||||
|
{ |
||||||
|
case null == $request->getPath(): |
||||||
|
case false == $request->getPath(): |
||||||
|
case '' == $request->getPath(): |
||||||
|
|
||||||
|
$response->setCode( |
||||||
|
30 |
||||||
|
); |
||||||
|
|
||||||
|
$response->setMeta( |
||||||
|
sprintf( |
||||||
|
'gemini://%s%s/', |
||||||
|
$config->geminiapp->server->host, |
||||||
|
$config->geminiapp->server->port == 1965 ? null : ':' . $config->geminiapp->server->port |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
// Main |
||||||
|
case '/' == $request->getPath(): |
||||||
|
case preg_match('/\/(N[A-z0-9]{33})/', $request->getPath(), $ns): |
||||||
|
|
||||||
|
$part = 1; |
||||||
|
$search = ''; |
||||||
|
$namespace = null; |
||||||
|
$h1 = []; |
||||||
|
$filter = []; |
||||||
|
$result = []; |
||||||
|
|
||||||
|
// Parse request |
||||||
|
parse_str( |
||||||
|
$request->getQuery(), |
||||||
|
$attribute |
||||||
|
); |
||||||
|
|
||||||
|
if (isset($attribute['search'])) |
||||||
|
{ |
||||||
|
$search = urldecode( |
||||||
|
(string) $attribute['search'] |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (isset($attribute['part'])) |
||||||
|
{ |
||||||
|
$part = (int) $attribute['part']; |
||||||
|
} |
||||||
|
|
||||||
|
if (isset($ns[1])) |
||||||
|
{ |
||||||
|
$namespace = $ns[1]; |
||||||
|
} |
||||||
|
|
||||||
|
// Build header |
||||||
|
if ($search) |
||||||
|
{ |
||||||
|
$h1[] = $search; |
||||||
|
$h1[] = $config->geminiapp->string->search; |
||||||
|
|
||||||
|
if ($part > 1) |
||||||
|
{ |
||||||
|
$h1[] = sprintf( |
||||||
|
'%s %d', |
||||||
|
$config->geminiapp->string->part, |
||||||
|
$part |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
$h1[] = $config->geminiapp->string->title; |
||||||
|
} |
||||||
|
|
||||||
|
else if ($namespace) |
||||||
|
{ |
||||||
|
// Find namespace alias |
||||||
|
if ($aliases = $index->get('_KEVA_NS', $filter)) |
||||||
|
{ |
||||||
|
foreach ($aliases as $alias) |
||||||
|
{ |
||||||
|
if ($alias['value']) |
||||||
|
{ |
||||||
|
$h1[] = $alias['value']; |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Not found, use hash |
||||||
|
else |
||||||
|
{ |
||||||
|
$h1[] = $namespace; |
||||||
|
} |
||||||
|
|
||||||
|
$h1[] = $config->geminiapp->string->title; |
||||||
|
} |
||||||
|
|
||||||
|
else |
||||||
|
{ |
||||||
|
if ($part > 1) |
||||||
|
{ |
||||||
|
$h1[] = sprintf( |
||||||
|
'%s %d', |
||||||
|
$config->geminiapp->string->part, |
||||||
|
$part |
||||||
|
); |
||||||
|
|
||||||
|
$h1[] = $config->geminiapp->string->title; |
||||||
|
} |
||||||
|
|
||||||
|
else |
||||||
|
{ |
||||||
|
$h1[] = $config->geminiapp->string->title; |
||||||
|
$h1[] = $config->geminiapp->string->description; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$result[] = sprintf( |
||||||
|
'# %s', |
||||||
|
implode( |
||||||
|
' · ', |
||||||
|
$h1 |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
// Menu |
||||||
|
$result[] = sprintf( |
||||||
|
'=> /search %s', |
||||||
|
$config->geminiapp->string->search |
||||||
|
); |
||||||
|
|
||||||
|
// Append links |
||||||
|
if ($namespace || $search || $part > 1) |
||||||
|
{ |
||||||
|
$result[] = sprintf( |
||||||
|
'=> / %s', |
||||||
|
$config->geminiapp->string->main |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
else |
||||||
|
{ |
||||||
|
foreach ($config->geminiapp->links as $link) |
||||||
|
{ |
||||||
|
$result[] = sprintf( |
||||||
|
'=> %s', |
||||||
|
$link |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Get records |
||||||
|
if ($namespace) |
||||||
|
{ |
||||||
|
$filter = |
||||||
|
[ |
||||||
|
'crc32_namespace' => crc32( |
||||||
|
$namespace |
||||||
|
) |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
$records = $index->get( |
||||||
|
$search, |
||||||
|
$filter, |
||||||
|
[ |
||||||
|
'time' => 'desc' |
||||||
|
], |
||||||
|
$part > 1 ? ($part - 1) * $config->geminiapp->pagination->limit : 0, |
||||||
|
$config->geminiapp->pagination->limit |
||||||
|
); |
||||||
|
|
||||||
|
// Append h2 |
||||||
|
$result[] = sprintf( |
||||||
|
'## %s', |
||||||
|
$search ? $config->geminiapp->string->results : $config->geminiapp->string->latest |
||||||
|
); |
||||||
|
|
||||||
|
if ($records) |
||||||
|
{ |
||||||
|
// Append latest records |
||||||
|
foreach ($records as $record) |
||||||
|
{ |
||||||
|
$result[] = sprintf( |
||||||
|
'### %s', |
||||||
|
$record['key'] |
||||||
|
); |
||||||
|
|
||||||
|
$result[] = $record['value']; |
||||||
|
|
||||||
|
$result[] = sprintf( |
||||||
|
'=> /%s %s in %d', |
||||||
|
$record['transaction'], |
||||||
|
date( |
||||||
|
'Y-m-d', |
||||||
|
$record['time'] |
||||||
|
), |
||||||
|
$record['block'] |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Append navigation |
||||||
|
$result[] = sprintf( |
||||||
|
'## %s', |
||||||
|
$config->geminiapp->string->navigation |
||||||
|
); |
||||||
|
|
||||||
|
// Pagination |
||||||
|
$older = []; |
||||||
|
$newer = []; |
||||||
|
|
||||||
|
if ($search) |
||||||
|
{ |
||||||
|
$older[] = sprintf( |
||||||
|
'search=%s', |
||||||
|
urlencode( |
||||||
|
$search |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
$newer[] = sprintf( |
||||||
|
'search=%s', |
||||||
|
urlencode( |
||||||
|
$search |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
$older[] = sprintf( |
||||||
|
'part=%d', |
||||||
|
$part + 1 |
||||||
|
); |
||||||
|
|
||||||
|
if ($part > 1) |
||||||
|
{ |
||||||
|
$newer[] = sprintf( |
||||||
|
'part=%d', |
||||||
|
$part - 1 |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Links |
||||||
|
if ($older) |
||||||
|
{ |
||||||
|
$result[] = sprintf( |
||||||
|
'=> /%s?%s %s', |
||||||
|
$namespace, |
||||||
|
implode( |
||||||
|
'&', |
||||||
|
$older, |
||||||
|
), |
||||||
|
$config->geminiapp->string->older |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if ($newer) |
||||||
|
{ |
||||||
|
$result[] = sprintf( |
||||||
|
'=> /%s?%s %s', |
||||||
|
$namespace, |
||||||
|
implode( |
||||||
|
'&', |
||||||
|
$newer, |
||||||
|
), |
||||||
|
$config->geminiapp->string->newer |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
else |
||||||
|
{ |
||||||
|
$result[] = $config->geminiapp->string->nothing; |
||||||
|
} |
||||||
|
|
||||||
|
$response->setContent( |
||||||
|
implode( |
||||||
|
PHP_EOL, |
||||||
|
$result |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
// Transaction page |
||||||
|
case preg_match('/\/([A-f0-9]{64})/', $request->getPath(), $attribute): |
||||||
|
|
||||||
|
$result = []; |
||||||
|
|
||||||
|
foreach ( |
||||||
|
$index->get( |
||||||
|
$attribute[1], |
||||||
|
[ |
||||||
|
'crc32_transaction' => crc32( |
||||||
|
$attribute[1] |
||||||
|
) |
||||||
|
], |
||||||
|
[ |
||||||
|
'time' => 'desc' |
||||||
|
], |
||||||
|
0, 1 |
||||||
|
) as $record |
||||||
|
) { |
||||||
|
if ($record['transaction'] == $attribute[1]) |
||||||
|
{ |
||||||
|
// Header |
||||||
|
$result[] = sprintf( |
||||||
|
'# %s', |
||||||
|
$record['key'] |
||||||
|
); |
||||||
|
|
||||||
|
// Body |
||||||
|
$result[] = sprintf( |
||||||
|
'``` %s', |
||||||
|
$config->geminiapp->string->value |
||||||
|
); |
||||||
|
|
||||||
|
$result[] = $record['value']; |
||||||
|
$result[] = '```'; |
||||||
|
|
||||||
|
$result[] = sprintf( |
||||||
|
'%s in %d', |
||||||
|
date( |
||||||
|
'Y-m-d', |
||||||
|
$record['time'] |
||||||
|
), |
||||||
|
$record['block'] |
||||||
|
); |
||||||
|
|
||||||
|
// Footer |
||||||
|
$result[] = sprintf( |
||||||
|
'## %s', |
||||||
|
$config->geminiapp->string->navigation |
||||||
|
); |
||||||
|
|
||||||
|
$result[] = sprintf( |
||||||
|
'=> /%s %s', |
||||||
|
$record['namespace'], |
||||||
|
$config->geminiapp->string->namespace |
||||||
|
); |
||||||
|
|
||||||
|
$result[] = sprintf( |
||||||
|
'=> /search %s', |
||||||
|
$config->geminiapp->string->search |
||||||
|
); |
||||||
|
|
||||||
|
$result[] = sprintf( |
||||||
|
'=> / %s', |
||||||
|
$config->geminiapp->string->main |
||||||
|
); |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$response->setContent( |
||||||
|
implode( |
||||||
|
PHP_EOL, |
||||||
|
$result |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
// Search page |
||||||
|
case '/search' == $request->getPath(): |
||||||
|
|
||||||
|
if (empty($request->getQuery())) |
||||||
|
{ |
||||||
|
$response->setCode( |
||||||
|
10 |
||||||
|
); |
||||||
|
|
||||||
|
$response->setMeta( |
||||||
|
$config->geminiapp->string->search |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
else |
||||||
|
{ |
||||||
|
$response->setCode( |
||||||
|
30 |
||||||
|
); |
||||||
|
|
||||||
|
$response->setMeta( |
||||||
|
sprintf( |
||||||
|
'gemini://%s%s/?search=%s', |
||||||
|
$config->geminiapp->server->host, |
||||||
|
$config->geminiapp->server->port == 1965 ? null : ':' . $config->geminiapp->server->port, |
||||||
|
$request->getQuery() |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
// Not found |
||||||
|
default: |
||||||
|
|
||||||
|
$response->setCode( |
||||||
|
51 |
||||||
|
); |
||||||
|
|
||||||
|
$response->setMeta( |
||||||
|
$config->geminiapp->string->nothing |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return $response; |
||||||
|
} |
||||||
|
); |
||||||
|
|
||||||
|
// Start server |
||||||
|
printf( |
||||||
|
_('Server "%s" started on %s:%d') . PHP_EOL, |
||||||
|
$argv[1], |
||||||
|
$config->geminiapp->server->host, |
||||||
|
$config->geminiapp->server->port |
||||||
|
); |
||||||
|
|
||||||
|
$server->start( |
||||||
|
$config->geminiapp->server->host, |
||||||
|
$config->geminiapp->server->port |
||||||
|
); |
Loading…
Reference in new issue