mirror of https://github.com/YGGverse/Yoda.git
yggverse
5 months ago
3 changed files with 457 additions and 375 deletions
@ -0,0 +1,339 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace Yggverse\Yoda\Model; |
||||||
|
|
||||||
|
/* |
||||||
|
* Single response API for multiple protocol providers |
||||||
|
* |
||||||
|
*/ |
||||||
|
class Response |
||||||
|
{ |
||||||
|
// Subject |
||||||
|
private \Yggverse\Net\Address $_address; |
||||||
|
|
||||||
|
// Async status |
||||||
|
private bool $_completed = false; |
||||||
|
private bool $_expired = false; |
||||||
|
|
||||||
|
// Response |
||||||
|
private ?string $_title = null; |
||||||
|
private ?string $_subtitle = null; |
||||||
|
private ?string $_tooltip = null; |
||||||
|
private ?string $_mime = null; |
||||||
|
private ?string $_data = null; |
||||||
|
private ?string $_redirect = null; |
||||||
|
private ?array $_request = null; |
||||||
|
|
||||||
|
// Config |
||||||
|
public const MIME_TEXT_GEMINI = 'text/gemini'; |
||||||
|
public const MIME_TEXT_PLAIN = 'text/plain'; |
||||||
|
|
||||||
|
public function __construct( |
||||||
|
string $request, |
||||||
|
int $timeout = 5 |
||||||
|
) { |
||||||
|
// Build address instance |
||||||
|
$this->_address = new \Yggverse\Net\Address( |
||||||
|
$request |
||||||
|
); |
||||||
|
|
||||||
|
// Detect protocol |
||||||
|
switch ($this->_address->getScheme()) |
||||||
|
{ |
||||||
|
case 'file': |
||||||
|
|
||||||
|
$this->_title = basename( |
||||||
|
$this->_address->getPath() |
||||||
|
); |
||||||
|
|
||||||
|
$this->_subtitle = $this->_address->getPath(); |
||||||
|
$this->_tooltip = $this->_address->getPath(); |
||||||
|
|
||||||
|
switch (true) |
||||||
|
{ |
||||||
|
case ( |
||||||
|
$list = \Yggverse\Yoda\Model\Filesystem::getList( |
||||||
|
$this->_address->getPath() |
||||||
|
) |
||||||
|
): // directory |
||||||
|
$tree = []; |
||||||
|
|
||||||
|
foreach ($list as $item) |
||||||
|
{ |
||||||
|
$tree[] = trim( |
||||||
|
sprintf( |
||||||
|
'=> file://%s %s', |
||||||
|
$item['path'], |
||||||
|
$item['name'] . ( |
||||||
|
$item['file'] ? null : '/' |
||||||
|
) |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
$this->_mime = self::MIME_TEXT_GEMINI; |
||||||
|
|
||||||
|
$this->_data = implode( |
||||||
|
PHP_EOL, |
||||||
|
$tree |
||||||
|
) . PHP_EOL; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case file_exists( |
||||||
|
$this->_address->getPath() |
||||||
|
) && is_readable( |
||||||
|
$this->_address->getPath() |
||||||
|
): |
||||||
|
$this->_data = strval( |
||||||
|
file_get_contents( |
||||||
|
$this->_address->getPath() |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
$this->_mime = strval( |
||||||
|
mime_content_type( |
||||||
|
$this->_address->getPath() |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
if ($this->_mime == self::MIME_TEXT_PLAIN) |
||||||
|
{ |
||||||
|
$extension = pathinfo( |
||||||
|
strval( |
||||||
|
$this->_address->getPath() |
||||||
|
), |
||||||
|
PATHINFO_EXTENSION |
||||||
|
); |
||||||
|
|
||||||
|
if (in_array($extension, ['gmi', 'gemini'])) |
||||||
|
{ |
||||||
|
$this->_mime = self::MIME_TEXT_GEMINI; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
|
||||||
|
$this->_title = _( |
||||||
|
'Failure' |
||||||
|
); |
||||||
|
|
||||||
|
$this->_data = _( |
||||||
|
'Could not open location' |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
$this->_completed = true; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 'gemini': // @TODO async |
||||||
|
|
||||||
|
$request = new \Yggverse\Gemini\Client\Request( |
||||||
|
$this->_address->get() |
||||||
|
); |
||||||
|
|
||||||
|
$response = new \Yggverse\Gemini\Client\Response( |
||||||
|
$request->getResponse() |
||||||
|
); |
||||||
|
|
||||||
|
// Route status code |
||||||
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-codes |
||||||
|
switch ($response->getCode()) |
||||||
|
{ |
||||||
|
case 10: // response expected |
||||||
|
case 11: // sensitive input |
||||||
|
|
||||||
|
$this->_request = |
||||||
|
[ |
||||||
|
'placeholder' => $response->getMeta(), |
||||||
|
'visible' => 11 !== $response->getCode() |
||||||
|
]; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 20: // ok |
||||||
|
|
||||||
|
$this->_data = $response->getBody(); |
||||||
|
|
||||||
|
switch (true) |
||||||
|
{ |
||||||
|
case str_contains( |
||||||
|
$response->getMeta(), |
||||||
|
self::MIME_TEXT_GEMINI |
||||||
|
): |
||||||
|
|
||||||
|
$this->_mime = self::MIME_TEXT_GEMINI; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case str_contains( |
||||||
|
$response->getMeta(), |
||||||
|
self::MIME_TEXT_PLAIN |
||||||
|
): |
||||||
|
|
||||||
|
$this->_mime = self::MIME_TEXT_PLAIN; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
|
||||||
|
throw new \Exception( |
||||||
|
sprintf( |
||||||
|
_('MIME type not implemented for %s'), |
||||||
|
$response->getMeta() |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
$this->_completed = true; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 31: // redirect |
||||||
|
// show link, no follow |
||||||
|
|
||||||
|
$this->_data = sprintf( |
||||||
|
'=> %s', |
||||||
|
$response->getMeta() |
||||||
|
); |
||||||
|
|
||||||
|
$this->_mime = self::MIME_TEXT_GEMINI; |
||||||
|
|
||||||
|
$this->_completed = true; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
|
||||||
|
$this->_title = _( |
||||||
|
'Oops!' |
||||||
|
); |
||||||
|
|
||||||
|
$this->_data = sprintf( |
||||||
|
'Could not open request (code: %d)', |
||||||
|
intval( |
||||||
|
$response->getCode() |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
$this->_mime = self::MIME_TEXT_GEMINI; |
||||||
|
|
||||||
|
$this->_completed = true; |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case 'nex': // @TODO async |
||||||
|
|
||||||
|
$this->_data = ( |
||||||
|
new \Yggverse\Nex\Client |
||||||
|
)->request( |
||||||
|
$this->_address->get() |
||||||
|
); |
||||||
|
|
||||||
|
$this->_mime = self::MIME_TEXT_PLAIN; // @TODO |
||||||
|
|
||||||
|
$this->_completed = true; |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case null: |
||||||
|
|
||||||
|
// Build gemini protocol address |
||||||
|
$address = new \Yggverse\Net\Address( |
||||||
|
sprintf( |
||||||
|
'gemini://%s', |
||||||
|
$this->_address->get() |
||||||
|
) |
||||||
|
); |
||||||
|
|
||||||
|
// Validate hostname |
||||||
|
if (filter_var( |
||||||
|
$address->getHost(), |
||||||
|
FILTER_VALIDATE_DOMAIN, |
||||||
|
FILTER_FLAG_HOSTNAME |
||||||
|
) |
||||||
|
) { |
||||||
|
// Request redirect |
||||||
|
$this->_redirect = $address->get(); |
||||||
|
} |
||||||
|
|
||||||
|
// Request redirect to search provider |
||||||
|
else |
||||||
|
{ |
||||||
|
// @TODO custom providers |
||||||
|
$this->_redirect = sprintf( |
||||||
|
'gemini://tlgs.one/search?%s', |
||||||
|
urlencode( |
||||||
|
$request |
||||||
|
) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return; |
||||||
|
|
||||||
|
default: |
||||||
|
|
||||||
|
throw new \Exception( |
||||||
|
_('Protocol not supported') |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function isCompleted(): bool |
||||||
|
{ |
||||||
|
return $this->_completed; |
||||||
|
} |
||||||
|
|
||||||
|
public function isExpired(): bool |
||||||
|
{ |
||||||
|
return $this->_expired; |
||||||
|
} |
||||||
|
|
||||||
|
public function getTitle(): ?string |
||||||
|
{ |
||||||
|
return $this->_title; |
||||||
|
} |
||||||
|
|
||||||
|
public function getSubtitle(): ?string |
||||||
|
{ |
||||||
|
return $this->_subtitle; |
||||||
|
} |
||||||
|
|
||||||
|
public function getTooltip(): ?string |
||||||
|
{ |
||||||
|
return $this->_tooltip; |
||||||
|
} |
||||||
|
|
||||||
|
public function getMime(): ?string |
||||||
|
{ |
||||||
|
return $this->_mime; |
||||||
|
} |
||||||
|
|
||||||
|
public function getData(): ?string |
||||||
|
{ |
||||||
|
return $this->_data; |
||||||
|
} |
||||||
|
|
||||||
|
public function getRedirect(): ?string |
||||||
|
{ |
||||||
|
return $this->_redirect; |
||||||
|
} |
||||||
|
|
||||||
|
public function getRequest(): ?array |
||||||
|
{ |
||||||
|
return $this->_request; |
||||||
|
} |
||||||
|
|
||||||
|
public function getLength(): ?int |
||||||
|
{ |
||||||
|
return mb_strlen( |
||||||
|
$this->_data |
||||||
|
); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue