rss-bridge/bridges/ItakuBridge.php
mruac a6a4502209
[Itaku] extend the number of images shown in a post (#3780)
* minor fixes

- extended itaku post if post does not have all images

* phpcbf

* .

* resolve deprecated explode param

yay null coalesces
2023-10-21 11:54:50 +02:00

702 lines
25 KiB
PHP

<?php
class ItakuBridge extends BridgeAbstract
{
const NAME = 'Itaku.ee Bridge';
const URI = 'https://itaku.ee';
const CACHE_TIMEOUT = 900; // 15mn
const MAINTAINER = 'mruac';
const DESCRIPTION = 'Bridges for Itaku.ee';
const PARAMETERS = [
'Image Search' => [
'text' => [
'name' => 'Text to search',
'title' => 'Search includes title, description and tags.',
'type' => 'text',
'exampleValue' => 'Text (incl. tags)'
],
'tags' => [
'name' => 'Tags to search',
'title' => 'Space seperated tags to include in search. Prepend with "-" to exclude, "~" for optional.',
'type' => 'text',
'exampleValue' => 'tag1 -tag2 ~tag3'
],
'order' => [
'name' => 'Sort by',
'type' => 'list',
'values' => [
'Trending' => '-hotness_score',
'Newest' => '-date_added',
'Oldest' => 'date_added',
'Top' => '-num_likes',
'Bottom' => 'num_likes'
],
'defaultValue' => '-date_added'
],
'range' => [
'name' => 'Date range',
'type' => 'list',
'values' => [
'Today' => 'today',
'Yesterday' => 'yesterday',
'Past 3 days' => '3_days',
'Past week' => 'week',
'Past month' => '30_days',
'Past year' => '365_days',
'All time' => ''
],
'defaultValue' => 'All time'
],
'video_only' => [
'name' => 'Video only?',
'type' => 'checkbox'
],
'rating_s' => [
'name' => 'Include SFW',
'type' => 'checkbox'
],
'rating_q' => [
'name' => 'Include Questionable',
'type' => 'checkbox'
],
'rating_e' => [
'name' => 'Include NSFW',
'type' => 'checkbox'
]
],
'Post Search' => [
'tags' => [
'name' => 'Tags to search',
'title' => 'Space seperated tags to include in search. Prepend with "-" to exclude, "~" for optional.',
'type' => 'text',
'exampleValue' => 'tag1 -tag2 ~tag3'
],
'order' => [
'name' => 'Sort by',
'type' => 'list',
'values' => [
'Trending' => '-hotness_score',
'Newest' => '-date_added',
'Oldest' => 'date_added',
'Top' => '-num_likes',
'Bottom' => 'num_likes'
],
'defaultValue' => '-date_added'
],
'range' => [
'name' => 'Date range',
'type' => 'list',
'values' => [
'Today' => 'today',
'Yesterday' => 'yesterday',
'Past 3 days' => '3_days',
'Past week' => 'week',
'Past month' => '30_days',
'Past year' => '365_days',
'All time' => ''
],
'defaultValue' => 'All time'
],
'text_only' => [
'name' => 'Only include posts with text?',
'type' => 'checkbox'
],
'rating_s' => [
'name' => 'Include SFW',
'type' => 'checkbox'
],
'rating_q' => [
'name' => 'Include Questionable',
'type' => 'checkbox'
],
'rating_e' => [
'name' => 'Include NSFW',
'type' => 'checkbox'
]
],
'User profile' => [
'user' => [
'name' => 'Username',
'type' => 'text',
'required' => true
],
'user_id' => [
'name' => 'User ID',
'type' => 'number',
'title' => 'User ID, if known.'
],
'reshares' => [
'name' => 'Include reshares',
'type' => 'checkbox'
],
'rating_s' => [
'name' => 'Include SFW',
'type' => 'checkbox'
],
'rating_q' => [
'name' => 'Include Questionable',
'type' => 'checkbox'
],
'rating_e' => [
'name' => 'Include NSFW',
'type' => 'checkbox'
]
],
'Home feed' => [
'order' => [
'name' => 'Sort by',
'type' => 'list',
'values' => [
'Trending' => '-hotness_score',
'Newest' => '-date_added'
],
'defaultValue' => '-date_added'
],
'range' => [
'name' => 'Date range',
'type' => 'list',
'values' => [
'Today' => 'today',
'Yesterday' => 'yesterday',
'Past 3 days' => '3_days',
'Past week' => 'week',
'Past month' => '30_days',
'Past year' => '365_days',
'All time' => ''
],
'defaultValue' => 'All time'
],
'reshares' => [
'name' => 'Include reshares',
'type' => 'checkbox'
],
'rating_s' => [
'name' => 'Include SFW',
'type' => 'checkbox'
],
'rating_q' => [
'name' => 'Include Questionable',
'type' => 'checkbox'
],
'rating_e' => [
'name' => 'Include NSFW',
'type' => 'checkbox'
]
]
];
public function collectData()
{
if ($this->queriedContext === 'Image Search') {
$opt = [
'text' => $this->getInput('text'),
'optional_tags' => [],
'negative_tags' => [],
'required_tags' => [],
'order' => $this->getInput('order'),
'range' => $this->getInput('range'),
'video_only' => $this->getInput('video_only'),
'rating_s' => $this->getInput('rating_s'),
'rating_q' => $this->getInput('rating_q'),
'rating_e' => $this->getInput('rating_e')
];
$tag_arr = explode(' ', $this->getInput('tags') ?? '');
foreach ($tag_arr as $str) {
switch ($str[0]) {
case '-':
$opt['negative_tags'][] = substr($str, 1);
break;
case '~':
$opt['optional_tags'][] = substr($str, 1);
break;
default:
$opt['required_tags'][] = substr($str, 1);
break;
}
}
$data = $this->getImagesSearch($opt);
foreach ($data['results'] as $record) {
$item = $this->getImage($record['id']);
$this->addItem($item);
}
}
if ($this->queriedContext === 'Post Search') {
$opt = [
'optional_tags' => [],
'negative_tags' => [],
'required_tags' => [],
'order' => $this->getInput('order'),
'range' => $this->getInput('range'),
'text_only' => $this->getInput('text_only'),
'rating_s' => $this->getInput('rating_s'),
'rating_q' => $this->getInput('rating_q'),
'rating_e' => $this->getInput('rating_e')
];
$tag_arr = explode(' ', $this->getInput('tags'));
foreach ($tag_arr as $str) {
switch ($str[0]) {
case '-':
$opt['negative_tags'][] = substr($str, 1);
break;
case '~':
$opt['optional_tags'][] = substr($str, 1);
break;
default:
$opt['required_tags'][] = substr($str, 1);
break;
}
}
$data = $this->getPostsSearch($opt);
foreach ($data['results'] as $record) {
$item = $this->getPost($record['id'], $record);
$this->addItem($item);
}
}
if (
$this->queriedContext === 'User profile'
|| $this->queriedContext === 'Home feed'
) {
$opt = [
'reshares' => $this->getInput('reshares'),
'rating_s' => $this->getInput('rating_s'),
'rating_q' => $this->getInput('rating_q'),
'rating_e' => $this->getInput('rating_e')
];
if ($this->queriedContext === 'User profile') {
$opt['order'] = '-date_added';
$opt['range'] = '';
$user_id = $this->getInput('user_id') ?? $this->getOwnerID($this->getInput('user'));
$data = $this->getFeed(
$opt,
$user_id
);
}
if ($this->queriedContext === 'Home feed') {
$opt['order'] = $this->getInput('order');
$opt['range'] = $this->getInput('range');
$data = $this->getFeed($opt);
}
foreach ($data['results'] as $record) {
switch ($record['content_type']) {
case 'reshare':
//get type of reshare and its id
$id = $record['content_object']['content_object']['id'];
switch ($record['content_object']['content_type']) {
case 'galleryimage':
$item = $this->getImage($id);
$item['title'] = "{$record['owner_username']} shared: {$item['title']}";
break;
case 'commission':
$item = $this->getCommission($id, $record['content_object']['content_object']);
$item['title'] = "{$record['owner_username']} shared: {$item['title']}";
break;
case 'post':
$item = $this->getPost($id, $record['content_object']['content_object']);
$item['title'] = "{$record['owner_username']} shared: {$item['title']}";
break;
};
break;
case 'galleryimage':
$item = $this->getImage($record['content_object']['id']);
break;
case 'commission':
$item = $this->getCommission($record['content_object']['id'], $record['content_object']);
break;
case 'post':
$item = $this->getPost($record['content_object']['id'], $record['content_object']);
break;
}
$this->addItem($item);
}
}
}
public function getName()
{
return self::NAME;
}
public function getURI()
{
return self::URI;
}
private function getImagesSearch(array $opt)
{
$url = self::URI . "/api/galleries/images/?by_following=false&date_range={$opt['range']}&ordering={$opt['order']}&is_video={$opt['video_only']}";
$url .= "&text={$opt['text']}&visibility=PUBLIC&visibility=PROFILE_ONLY&page=1&page_size=30&format=json";
if (sizeof($opt['optional_tags']) > 0) {
foreach ($opt['optional_tags'] as $tag) {
$url .= "&optional_tags=$tag";
}
}
if (sizeof($opt['negative_tags']) > 0) {
foreach ($opt['negative_tags'] as $tag) {
$url .= "&negative_tags=$tag";
}
}
if (sizeof($opt['required_tags']) > 0) {
foreach ($opt['required_tags'] as $tag) {
$url .= "&required_tags=$tag";
}
}
if ($opt['rating_s']) {
$url .= '&maturity_rating=SFW';
}
if ($opt['rating_q']) {
$url .= '&maturity_rating=Questionable';
}
if ($opt['rating_e']) {
$url .= '&maturity_rating=NSFW';
}
return $this->getData($url, false, true);
}
private function getPostsSearch(array $opt)
{
$url = self::URI . "/api/posts/?by_following=false&date_range={$opt['range']}&ordering={$opt['order']}";
$url .= '&visibility=PUBLIC&visibility=PROFILE_ONLY&page=1&page_size=30&format=json';
if (sizeof($opt['optional_tags']) > 0) {
foreach ($opt['optional_tags'] as $tag) {
$url .= "&optional_tags=$tag";
}
}
if (sizeof($opt['negative_tags']) > 0) {
foreach ($opt['negative_tags'] as $tag) {
$url .= "&negative_tags=$tag";
}
}
if (sizeof($opt['required_tags']) > 0) {
foreach ($opt['required_tags'] as $tag) {
$url .= "&required_tags=$tag";
}
}
if ($opt['rating_s']) {
$url .= '&maturity_rating=SFW';
}
if ($opt['rating_q']) {
$url .= '&maturity_rating=Questionable';
}
if ($opt['rating_e']) {
$url .= '&maturity_rating=NSFW';
}
return $this->getData($url, false, true);
}
private function getFeed(array $opt, $ownerID = null)
{
$url = self::URI . "/api/feed/?date_range={$opt['range']}&ordering={$opt['order']}&page=1&page_size=30&format=json";
if (is_null($ownerID)) {
$url .= '&visibility=PUBLIC&by_following=false';
} else {
$url .= "&owner={$ownerID}";
}
if (!$opt['reshares']) {
$url .= '&hide_reshares=true';
}
if ($opt['rating_s']) {
$url .= '&maturity_rating=SFW';
}
if ($opt['rating_q']) {
$url .= '&maturity_rating=Questionable';
}
if ($opt['rating_e']) {
$url .= '&maturity_rating=NSFW';
}
return $this->getData($url, false, true);
}
private function getOwnerID($username)
{
$url = self::URI . "/api/user_profiles/{$username}/?format=json";
$data = $this->getData($url, true, true)
or returnServerError("Could not load $url");
return $data['owner'];
}
private function getPost($id, array $metadata = null)
{
if (isset($metadata) && sizeof($metadata['gallery_images']) < $metadata['num_images']) {
$metadata = null; //force re-fetch of metadata
}
$uri = self::URI . '/posts/' . $id;
$url = self::URI . '/api/posts/' . $id . '/?format=json';
$data = $metadata ?? $this->getData($url, true, true)
or returnServerError("Could not load $url");
$content_str = nl2br($data['content']);
$content = "<p>{$content_str}</p><br/>"; //TODO: Add link and itaku user mention detection and convert into links.
if (array_key_exists('tags', $data) && sizeof($data['tags']) > 0) {
$tag_types = [
'ARTIST' => '',
'COPYRIGHT' => '',
'CHARACTER' => '',
'SPECIES' => '',
'GENERAL' => '',
'META' => ''
];
foreach ($data['tags'] as $tag) {
$url = self::URI . '/tags/' . $tag['id'];
$str = "<a href=\"{$url}\">#{$tag['name']}</a> ";
$tag_types[$tag['tag_type']] .= $str;
}
foreach ($tag_types as $type => $str) {
if (strlen($str) > 0) {
$content .= "🏷 <b>{$type}:</b> {$str}<br/>";
}
}
}
if (sizeof($data['folders']) > 0) {
$content .= '📁 In Folder(s): ';
foreach ($data['folders'] as $folder) {
$url = self::URI . '/profile/' . $data['owner_username'] . '/posts/' . $folder['id'];
$content .= "<a href=\"{$url}\">#{$folder['title']}</a> ";
}
}
$content .= '<hr/>';
if (sizeof($data['gallery_images']) > 0) {
foreach ($data['gallery_images'] as $media) {
$title = $media['title'];
$url = self::URI . '/images/' . $media['id'];
$src = $media['image_xl'];
$content .= '<p>';
$content .= "<a href=\"{$url}\"><b>{$title}</b></a><br/>";
if ($media['is_thumbnail_for_video']) {
$url = self::URI . '/api/galleries/images/' . $media['id'] . '/?format=json';
$media_data = $this->getData($url, true, true)
or returnServerError("Could not load $url");
$content .= "<video controls src=\"{$media_data['video']['video']}\" poster=\"{$media['image_xl']}\"/>";
} else {
$content .= "<a href=\"{$url}\"><img src=\"{$src}\"></a>";
}
$content .= '</p><br/>';
}
}
return [
'uri' => $uri,
'title' => $data['title'],
'timestamp' => $data['date_added'],
'author' => $data['owner_username'],
'content' => $content,
'categories' => ['post'],
'uid' => $uri
];
}
private function getCommission($id, array $metadata = null)
{
$url = self::URI . '/api/commissions/' . $id . '/?format=json';
$uri = self::URI . '/commissions/' . $id;
// Debug::log(var_dump($metadata));
$data = $metadata ?? $this->getData($url, true, true)
or returnServerError("Could not load $url");
$content_str = nl2br($data['description']);
$content = "<p>{$content_str}</p><br>"; //TODO: Add link and itaku user mention detection and convert into links.
if (array_key_exists('tags', $data) && sizeof($data['tags']) > 0) {
// $content .= "🏷 Tag(s): ";
$tag_types = [
'ARTIST' => '',
'COPYRIGHT' => '',
'CHARACTER' => '',
'SPECIES' => '',
'GENERAL' => '',
'META' => ''
];
foreach ($data['tags'] as $tag) {
$url = self::URI . '/tags/' . $tag['id'];
$str = "<a href=\"{$url}\">#{$tag['name']}</a> ";
$tag_types[$tag['tag_type']] .= $str;
}
foreach ($tag_types as $type => $str) {
if (strlen($str) > 0) {
$content .= "🏷 <b>{$type}:</b> {$str}<br/>";
}
}
}
if (array_key_exists('reference_gallery_sections', $data) && sizeof($data['reference_gallery_sections']) > 0) {
$content .= '📁 Example folder(s): ';
foreach ($data['folders'] as $folder) {
$url = self::URI . '/profile/' . $data['owner_username'] . '/gallery/' . $folder['id'];
$folder_name = $folder['title'];
if (!is_null($folder['group'])) {
$folder_name = $folder['group']['title'] . '/' . $folder_name;
}
$content .= "<a href=\"{$url}\">#{$folder_name}</a> ";
}
}
$content .= '<hr/>';
if (!is_null($data['thumbnail_detail'])) {
$content .= '<p>';
$content .= "<a href=\"{$uri}\"><b>{$data['thumbnail_detail']['title']}</b></a><br/>";
if ($data['thumbnail_detail']['is_thumbnail_for_video']) {
$url = self::URI . '/api/galleries/images/' . $data['thumbnail_detail']['id'] . '/?format=json';
$media_data = $this->getData($url, true, true)
or returnServerError("Could not load $url");
$content .= "<video controls src=\"{$media_data['video']['video']}\" poster=\"{$data['thumbnail_detail']['image_lg']}\"/>";
} else {
$content .= "<a href=\"{$uri}\"><img src=\"{$data['thumbnail_detail']['image_lg']}\"></a>";
}
$content .= '</p>';
}
return [
'uri' => $uri,
'title' => "{$data['comm_type']}: {$data['title']}",
'timestamp' => $data['date_added'],
'author' => $data['owner_username'],
'content' => $content,
'categories' => ['commission', $data['comm_type']],
'uid' => $uri
];
}
private function getImage($id /* array $metadata = null */) //$metadata disabled due to no essential information available in ./api/feed/ or ./api/galleries/images/ results.
{
$uri = self::URI . '/images/' . $id;
$url = self::URI . '/api/galleries/images/' . $id . '/?format=json';
$data = /* $metadata ?? */ $this->getData($url, true, true)
or returnServerError("Could not load $url");
$content_str = nl2br($data['description']);
$content = "<p>{$content_str}</p><br/>"; //TODO: Add link and itaku user mention detection and convert into links.
if (array_key_exists('tags', $data) && sizeof($data['tags']) > 0) {
// $content .= "🏷 Tag(s): ";
$tag_types = [
'ARTIST' => '',
'COPYRIGHT' => '',
'CHARACTER' => '',
'SPECIES' => '',
'GENERAL' => '',
'META' => ''
];
foreach ($data['tags'] as $tag) {
$url = self::URI . '/tags/' . $tag['id'];
$str = "<a href=\"{$url}\">#{$tag['name']}</a> ";
$tag_types[$tag['tag_type']] .= $str;
}
foreach ($tag_types as $type => $str) {
if (strlen($str) > 0) {
$content .= "🏷 <b>{$type}:</b> {$str}<br/>";
}
}
}
if (array_key_exists('sections', $data) && sizeof($data['sections']) > 0) {
$content .= '📁 In Folder(s): ';
foreach ($data['sections'] as $folder) {
$url = self::URI . '/profile/' . $data['owner_username'] . '/gallery/' . $folder['id'];
$folder_name = $folder['title'];
if (!is_null($folder['group'])) {
$folder_name = $folder['group']['title'] . '/' . $folder_name;
}
$content .= "<a href=\"{$url}\">#{$folder_name}</a> ";
}
}
$content .= '<hr/>';
if (array_key_exists('is_thumbnail_for_video', $data)) {
$url = self::URI . '/api/galleries/images/' . $data['id'] . '/?format=json';
$media_data = $this->getData($url, true, true)
or returnServerError("Could not load $url");
$content .= "<video controls src=\"{$media_data['video']['video']}\" poster=\"{$data['image_xl']}\"/>";
} else {
if (array_key_exists('video', $data) && is_null($data['video'])) {
$content .= "<a href=\"{$uri}\"><img src=\"{$data['image_xl']}\"></a>";
} else {
$content .= "<video controls src=\"{$data['video']['video']}\" poster=\"{$data['image_xl']}\"/>";
}
}
return [
'uri' => $uri,
'title' => $data['title'],
'timestamp' => $data['date_added'],
'author' => $data['owner_username'],
'content' => $content,
'categories' => ['image'],
'uid' => $uri
];
}
private function getData(string $url, bool $cache = false, bool $getJSON = false, array $httpHeaders = [], array $curlOptions = [])
{
// Debug::log($url);
if ($getJSON) { //get JSON object
if ($cache) {
$data = $this->loadCacheValue($url);
if (is_null($data)) {
$data = getContents($url, $httpHeaders, $curlOptions) or returnServerError("Could not load $url");
$this->saveCacheValue($url, $data);
}
} else {
$data = getContents($url, $httpHeaders, $curlOptions) or returnServerError("Could not load $url");
}
return json_decode($data, true);
} else { //get simpleHTMLDOM object
if ($cache) {
$html = getSimpleHTMLDOMCached($url, 86400); // 24 hours
} else {
$html = getSimpleHTMLDOM($url);
}
$html = defaultLinkTo($html, $url);
return $html;
}
}
private function addItem($item)
{
if (is_null($item)) {
return;
}
if (is_array($item) || is_object($item)) {
$this->items[] = $item;
} else {
returnServerError("Incorrectly parsed item. Check the code!\nType: " . gettype($item) . "\nprint_r(item:)\n" . var_dump($item));
}
}
}