Browse Source

implement post commission

main
ghost 10 months ago
parent
commit
d7f751c6c2
  1. 2
      README.md
  2. 2
      composer.json
  3. 11
      data.sql
  4. 19
      example/config.json
  5. 148
      src/controller/room.php
  6. 237
      src/crontab.php
  7. 38
      src/server.php
  8. 15
      src/view/pool.gemini

2
README.md

@ -31,6 +31,7 @@ KevaChat is distributed chat platform for open, uncensored and privacy respectab
## Install ## Install
* `apt install git composer memcached sqlite3 php-curl php-memcached php-sqlite3 php-mbstring`
* `git clone https://github.com/kevachat/geminiapp.git` * `git clone https://github.com/kevachat/geminiapp.git`
* `cd geminiapp` * `cd geminiapp`
* `composer update` * `composer update`
@ -41,6 +42,7 @@ KevaChat is distributed chat platform for open, uncensored and privacy respectab
* `cp example/config.json host/127.0.0.1/config.json` * `cp example/config.json host/127.0.0.1/config.json`
* `cd host/127.0.0.1` * `cd 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"` * `openssl req -x509 -newkey rsa:4096 -keyout key.rsa -out cert.pem -days 365 -nodes -subj "/CN=127.0.0.1"`
* `* * * * * php src/crontab.php 127.0.0.1` - if post commission enabled
## Start ## Start

2
composer.json

@ -10,7 +10,7 @@
}, },
"require": { "require": {
"yggverse/titan-ii": "^1.0", "yggverse/titan-ii": "^1.0",
"kevachat/kevacoin": "^1.6", "kevachat/kevacoin": "^1.7",
"clitor-is-protocol/kevacoin": "^1.0", "clitor-is-protocol/kevacoin": "^1.0",
"yggverse/cache": "^0.4" "yggverse/cache": "^0.4"
} }

11
data.sql

@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS "pool" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"time" INTEGER NOT NULL,
"sent" INTEGER NOT NULL,
"expired" INTEGER NOT NULL,
"cost" INTEGER NOT NULL,
"address" VARCHAR NOT NULL,
"namespace" VARCHAR NOT NULL,
"key" VARCHAR NOT NULL,
"value" TEXT NOT NULL
);

19
example/config.json

@ -36,6 +36,16 @@
"namespace":"geminiapp" "namespace":"geminiapp"
} }
}, },
"sqlite":
{
"server":
{
"name":"127.0.0.1/data.db",
"user":"",
"password":"",
"timeout":600
}
},
"kevachat": "kevachat":
{ {
"about": "about":
@ -62,6 +72,15 @@
"value": "value":
{ {
"regex":"/.*/ui" "regex":"/.*/ui"
},
"pool":
{
"timeout":3600,
"confirmations":1
},
"cost":
{
"kva":1
} }
}, },
"user": "user":

148
src/controller/room.php

@ -10,6 +10,7 @@ class Room
private \Kevachat\Kevacoin\Client $_kevacoin; private \Kevachat\Kevacoin\Client $_kevacoin;
private \Yggverse\Cache\Memory $_memory; private \Yggverse\Cache\Memory $_memory;
private \PDO $_database;
public function __construct($config) public function __construct($config)
{ {
@ -40,6 +41,38 @@ class Room
$this->_config->kevacoin->server->username, $this->_config->kevacoin->server->username,
$this->_config->kevacoin->server->password $this->_config->kevacoin->server->password
); );
// Init database
$this->_database = new \PDO(
sprintf(
'sqlite:%s/../../host/%s',
__DIR__,
$config->sqlite->server->name
),
$config->sqlite->server->user,
$config->sqlite->server->password
);
$this->_database->setAttribute(
\PDO::ATTR_ERRMODE,
\PDO::ERRMODE_EXCEPTION
);
$this->_database->setAttribute(
\PDO::ATTR_DEFAULT_FETCH_MODE,
\PDO::FETCH_OBJ
);
$this->_database->setAttribute(
\PDO::ATTR_TIMEOUT,
$config->sqlite->server->timeout
);
$this->_database->query(
file_get_contents(
__DIR__ . '/../../data.sql'
)
);
} }
public function list(): string public function list(): string
@ -294,7 +327,7 @@ class Room
); );
} }
public function post(string $namespace, ?string $txid, int $session, string $message): ?string public function post(string $namespace, ?string $mention, int $session, string $message, ?string &$address = null): null|int|string
{ {
// Validate funds available yet // Validate funds available yet
if (1 > $this->_kevacoin->getBalance()) if (1 > $this->_kevacoin->getBalance())
@ -322,9 +355,9 @@ class Room
); );
// Append mention if provided // Append mention if provided
if ($txid) if ($mention)
{ {
$message = '@' . $txid . PHP_EOL . $message; $message = '@' . $mention . PHP_EOL . $message;
} }
// Validate final message length // Validate final message length
@ -333,32 +366,108 @@ class Room
return null; return null;
} }
// Send message // Cleanup session
if $this->_memory->delete(
( $session
$txid = $this->_kevacoin->kevaPut( );
// Payment required, get new address and save message to the pending pool
if ($this->_config->kevachat->post->cost->kva > 0)
{
$time = time();
$query = $this->_database->prepare(
'INSERT INTO `pool` (
`time`,
`sent`,
`expired`,
`cost`,
`address`,
`namespace`,
`key`,
`value`
) VALUES (
:time,
:sent,
:expired,
:cost,
:address,
:namespace,
:key,
:value
)'
);
$query->execute(
[
':time' => $time,
':sent' => 0,
':expired' => 0,
':cost' => $this->_config->kevachat->post->cost->kva,
':address' => $address = $this->_kevacoin->getNewAddress(),
':namespace' => $namespace,
':key' => sprintf('%s@anon', $time),
':value' => $message
]
);
return $this->_database->lastInsertId();
}
// Publications free, send post immediately
else
{
return $this->_kevacoin->kevaPut(
$namespace, $namespace,
sprintf( sprintf(
'%s@anon', '%s@anon',
time() time()
), ),
$message $message
)
)
{
// Cleanup session
$this->_memory->delete(
$session
); );
}
// Success
return $txid;
} }
return null; public function sent(string $namespace, mixed $id, ?string $address)
{
// Transaction requires payment
if ($address)
{
return str_replace(
[
'{logo}',
'{id}',
'{address}',
'{amount}',
'{expires}',
'{room}'
],
[
file_get_contents(
__DIR__ . '/../../logo.ascii'
),
$id,
$address,
$this->_config->kevachat->post->cost->kva,
date(
'c',
$this->_config->kevachat->post->pool->timeout + time()
),
$this->_link(
sprintf(
'/room/%s',
$namespace
)
)
],
file_get_contents(
__DIR__ . '/../view/pool.gemini'
)
);
} }
public function sent(string $namespace, string $txid) // Free post
else
{ {
return str_replace( return str_replace(
[ [
@ -370,7 +479,7 @@ class Room
file_get_contents( file_get_contents(
__DIR__ . '/../../logo.ascii' __DIR__ . '/../../logo.ascii'
), ),
$txid, $id,
$this->_link( $this->_link(
sprintf( sprintf(
'/room/%s', '/room/%s',
@ -383,6 +492,7 @@ class Room
) )
); );
} }
}
private function _post( private function _post(
string $namespace, string $namespace,

237
src/crontab.php

@ -0,0 +1,237 @@
<?php
// Check arguments
if (empty($argv[1]))
{
exit(
sprintf(
_('[%s] [error] Configured hostname required as argument!'),
date('c')
) . PHP_EOL
);
}
// Check host configured
if (!file_exists(__DIR__ . '/../host/' . $argv[1] . '/config.json'))
{
exit(
sprintf(
_('[%s] [error] Host "%s" not configured!'),
date('c'),
$argv[1]
) . PHP_EOL
);
}
// Prevent multi-thread execution
$semaphore = sem_get(
crc32(__FILE__ . $argv[1]), 1
);
if (false === sem_acquire($semaphore, true))
{
exit(
sprintf(
_('[%s] [warning] Process locked by another thread!'),
date('c')
) . PHP_EOL
);
}
// Init config
$config = json_decode(
file_get_contents(
__DIR__ . '/../host/' . $argv[1] . '/config.json'
)
);
// Load dependencies
require_once __DIR__ . '/../vendor/autoload.php';
// Init kevacoin
try
{
$kevacoin = new \Kevachat\Kevacoin\Client(
$config->kevacoin->server->protocol,
$config->kevacoin->server->host,
$config->kevacoin->server->port,
$config->kevacoin->server->username,
$config->kevacoin->server->password
);
}
catch (Exception $exception)
{
exit(
print_r(
$exception,
true
)
);
}
// Init database
try
{
$database = new \PDO(
sprintf(
'sqlite:%s/../host/%s',
__DIR__,
$config->sqlite->server->name
),
$config->sqlite->server->user,
$config->sqlite->server->password
);
$database->setAttribute(
\PDO::ATTR_ERRMODE,
\PDO::ERRMODE_EXCEPTION
);
$database->setAttribute(
\PDO::ATTR_DEFAULT_FETCH_MODE,
\PDO::FETCH_OBJ
);
$database->setAttribute(
\PDO::ATTR_TIMEOUT,
$config->sqlite->server->timeout
);
$database->query(
file_get_contents(
__DIR__ . '/../data.sql'
)
);
}
catch (Exception $exception)
{
exit(
print_r(
$exception,
true
)
);
}
// Init room list
$rooms = [];
foreach ((array) $kevacoin->kevaListNamespaces() as $value)
{
$rooms[$value['namespaceId']] = mb_strtolower($value['displayName']);
}
// Skip room lock events
if (empty($rooms))
{
exit(
sprintf(
_('[%s] [error] Could not init room list!'),
date('c')
) . PHP_EOL
);
}
// Process pool queue
$total = 0;
foreach ($database->query('SELECT * FROM `pool` WHERE `sent` = 0 AND `expired` = 0')->fetchAll() as $pool)
{
// Payment received, send to blockchain
if ($kevacoin->getReceivedByAddress($pool->address, $config->kevachat->post->pool->confirmations) >= $pool->cost)
{
// Check physical wallet balance
if ($kevacoin->getBalance() <= $pool->getCost())
{
exit(
sprintf(
_('[%s] [error] Insufficient wallet funds!'),
date('c')
) . PHP_EOL
);
}
// Check namespace is valid
if (!isset($rooms[$pool->namespace]))
{
print(
sprintf(
_('[%s] [warning] Could not found "%s" in room list!'),
date('c'),
$pool->namespace
) . PHP_EOL
);
continue;
}
// Send to blockchain
if ($txid = $kevacoin->kevaPut($pool->namespace, $pool->key, $pool->value))
{
// Update status
$database->query(
sprintf(
'UPDATE `pool` SET `sent` = %d WHERE `id` = %d LIMIT 1',
time(),
$pool->id
)
);
print(
sprintf(
_('[%s] [notice] Record ID "%d" sent to blockchain with transaction ID "%s"'),
date('c'),
$pool->id,
$txid
) . PHP_EOL
);
}
else
{
print(
sprintf(
_('[%s] [error] Could not send record ID "%d" to blockchain!'),
date('c'),
$pool->id,
$txid
) . PHP_EOL
);
}
}
// Record expired
else if (time() >= $pool->time + $this->_config->kevachat->post->pool->timeout)
{
// Update status
$database->query(
sprintf(
'UPDATE `pool` SET `expired` = %d WHERE `id` = %d LIMIT 1',
time(),
$pool->id
)
);
print(
sprintf(
_('[%s] [notice] Record ID "%d" expired.'),
date('c'),
$pool->id,
$txid
) . PHP_EOL
);
}
// Update counter
$total++;
}
print(
sprintf(
_('[%s] [notice] Records processed: %d'),
date('c'),
$total
) . PHP_EOL
);

38
src/server.php

@ -160,10 +160,23 @@ $server->setHandler(
{ {
// Request post message // Request post message
if (empty($request->getQuery())) if (empty($request->getQuery()))
{
if ($config->kevachat->post->cost->kva)
{
$response->setMeta(
sprintf(
_('Enter your message text (cost: %s KVA)'),
$config->kevachat->post->cost->kva
)
);
}
else
{ {
$response->setMeta( $response->setMeta(
'text/plain' _('Enter your message text:')
); );
}
$response->setCode( $response->setCode(
10 10
@ -182,9 +195,9 @@ $server->setHandler(
); );
// Success, redirect to this room page // Success, redirect to this room page
if ($txid = $room->post($matches[1], null, $matches[2], $request->getQuery())) if ($id = $room->post($matches[1], null, $matches[2], $request->getQuery(), $address))
{ {
if ($result = $room->sent($matches[1], $txid)) if ($result = $room->sent($matches[1], $id, $address))
{ {
$response->setContent( $response->setContent(
$result $result
@ -204,10 +217,23 @@ $server->setHandler(
{ {
// Request post message // Request post message
if (empty($request->getQuery())) if (empty($request->getQuery()))
{
if ($config->kevachat->post->cost->kva)
{
$response->setMeta(
sprintf(
_('Enter your message text (cost: %s KVA)'),
$config->kevachat->post->cost->kva
)
);
}
else
{ {
$response->setMeta( $response->setMeta(
'text/plain' _('Enter your message text:')
); );
}
$response->setCode( $response->setCode(
10 10
@ -226,9 +252,9 @@ $server->setHandler(
); );
// Success, redirect to this room page // Success, redirect to this room page
if ($txid = $room->post($matches[1], $matches[2], $matches[3], $request->getQuery())) if ($id = $room->post($matches[1], $matches[2], $matches[3], $request->getQuery(), $address))
{ {
if ($result = $room->sent($matches[1], $txid)) if ($result = $room->sent($matches[1], $id, $address))
{ {
$response->setContent( $response->setContent(
$result $result

15
src/view/pool.gemini

@ -0,0 +1,15 @@
```{logo}```
# Success!
Your message saved in pool with ID #{id}
Send exactly {amount} KVA to this address:
```
{address}
```
Invoice expires at {expires}
=> {room} Done!
Loading…
Cancel
Save