use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\System;
+use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
-use Friendica\Model\ItemURI;
use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Model\Tag;
use GuzzleHttp\Subscriber\Oauth\Oauth1;
define('TUMBLR_DEFAULT_POLL_INTERVAL', 10); // given in minutes
+define('TUMBLR_DEFAULT_MAXIMUM_TAGS', 10);
function tumblr_install()
{
Hook::register('connector_settings_post', __FILE__, 'tumblr_settings_post');
Hook::register('cron', __FILE__, 'tumblr_cron');
Hook::register('support_follow', __FILE__, 'tumblr_support_follow');
+ Hook::register('support_probe', __FILE__, 'tumblr_support_probe');
Hook::register('follow', __FILE__, 'tumblr_follow');
Hook::register('unfollow', __FILE__, 'tumblr_unfollow');
Hook::register('block', __FILE__, 'tumblr_block');
function tumblr_check_item_notification(array &$notification_data)
{
- if (!tumblr_enabled_for_user($notification_data['uid'])) {
+ if (!tumblr_enabled_for_user($notification_data['uid'])) {
return;
}
return;
}
- $own_user = Contact::selectFirst(['url', 'alias'], ['uid' => $notification_data['uid'], 'poll' => 'tumblr::'.$page]);
+ $own_user = Contact::selectFirst(['url', 'alias'], ['network' => Protocol::TUMBLR, 'uid' => [0, $notification_data['uid']], 'poll' => 'tumblr::' . $page]);
if ($own_user) {
$notification_data['profiles'][] = $own_user['url'];
$notification_data['profiles'][] = $own_user['alias'];
return;
}
- Logger::debug('Search for tumblr blog', ['url' => $hookData['uri']]);
-
$hookData['result'] = tumblr_get_contact_by_url($hookData['uri']);
+
+ // Authoritative probe should set the result even if the probe was unsuccessful
+ if ($hookData['network'] == Protocol::TUMBLR && empty($hookData['result'])) {
+ $hookData['result'] = [];
+ }
}
function tumblr_item_by_link(array &$hookData)
if (!preg_match('#^https?://www\.tumblr.com/blog/view/(.+)/(\d+).*#', $hookData['uri'], $matches) && !preg_match('#^https?://www\.tumblr.com/(.+)/(\d+).*#', $hookData['uri'], $matches)) {
return;
}
-
+
Logger::debug('Found tumblr post', ['url' => $hookData['uri'], 'blog' => $matches[1], 'id' => $matches[2]]);
$parameters = ['id' => $matches[2], 'reblog_info' => false, 'notes_info' => false, 'npf' => false];
Logger::debug('Got post', ['blog' => $matches[1], 'id' => $matches[2], 'result' => $result->response->posts]);
if (!empty($result->response->posts)) {
- $hookData['item_id'] = tumblr_process_post($result->response->posts[0], $hookData['uid']);
+ $hookData['item_id'] = tumblr_process_post($result->response->posts[0], $hookData['uid'], Item::PR_FETCHED);
}
}
}
}
+function tumblr_support_probe(array &$data)
+{
+ if ($data['protocol'] == Protocol::TUMBLR) {
+ $data['result'] = true;
+ }
+}
+
function tumblr_follow(array &$hook_data)
{
$uid = DI::userSession()->getLocalUserId();
'$submit' => DI::l10n()->t('Save Settings'),
'$consumer_key' => ['consumer_key', DI::l10n()->t('Consumer Key'), DI::config()->get('tumblr', 'consumer_key'), ''],
'$consumer_secret' => ['consumer_secret', DI::l10n()->t('Consumer Secret'), DI::config()->get('tumblr', 'consumer_secret'), ''],
+ '$max_tags' => ['max_tags', DI::l10n()->t('Maximum tags'), DI::config()->get('tumblr', 'max_tags') ?? TUMBLR_DEFAULT_MAXIMUM_TAGS, DI::l10n()->t('Maximum number of tags that a user can follow. Enter 0 to deactivate the feature.')],
]);
}
{
DI::config()->set('tumblr', 'consumer_key', trim($_POST['consumer_key'] ?? ''));
DI::config()->set('tumblr', 'consumer_secret', trim($_POST['consumer_secret'] ?? ''));
+ DI::config()->set('tumblr', 'max_tags', max(0, intval($_POST['max_tags'] ?? '')));
}
function tumblr_settings(array &$data)
return;
}
- $enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'tumblr', 'post', false);
- $def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'tumblr', 'post_by_default', false);
- $import = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'tumblr', 'import', false);
+ $enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'tumblr', 'post') ?? false;
+ $def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'tumblr', 'post_by_default') ?? false;
+ $import = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'tumblr', 'import') ?? false;
+ $tags = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'tumblr', 'tags') ?? [];
+
+ $max_tags = DI::config()->get('tumblr', 'max_tags') ?? TUMBLR_DEFAULT_MAXIMUM_TAGS;
+ $tags_str = implode(', ', $tags);
$cachekey = 'tumblr-blogs-' . DI::userSession()->getLocalUserId();
$blogs = DI::cache()->get($cachekey);
if (empty($blogs)) {
'$enable' => ['tumblr', DI::l10n()->t('Enable Tumblr Post Addon'), $enabled],
'$bydefault' => ['tumblr_bydefault', DI::l10n()->t('Post to Tumblr by default'), $def_enabled],
'$import' => ['tumblr_import', DI::l10n()->t('Import the remote timeline'), $import],
+ '$tags' => ['tags', DI::l10n()->t('Subscribed tags'), $tags_str, DI::l10n()->t('Comma separated list of up to %d tags that will be imported additionally to the timeline', $max_tags)],
'$page_select' => $page_select ?? '',
]);
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'tumblr', 'page', $_POST['tumblr_page']);
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'tumblr', 'post_by_default', intval($_POST['tumblr_bydefault']));
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'tumblr', 'import', intval($_POST['tumblr_import']));
+
+ $max_tags = DI::config()->get('tumblr', 'max_tags') ?? TUMBLR_DEFAULT_MAXIMUM_TAGS;
+ $tags = [];
+ foreach (explode(',', $_POST['tags']) as $tag) {
+ if (count($tags) < $max_tags) {
+ $tags[] = trim($tag, ' #');
+ }
+ }
+
+ DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'tumblr', 'tags', $tags);
}
}
if ($last) {
$next = $last + ($poll_interval * 60);
if ($next > time()) {
- Logger::notice('poll intervall not reached');
+ Logger::notice('poll interval not reached');
return;
}
}
Logger::notice('importing timeline - start', ['user' => $pconfig['uid']]);
tumblr_fetch_dashboard($pconfig['uid']);
+ tumblr_fetch_tags($pconfig['uid']);
Logger::notice('importing timeline - done', ['user' => $pconfig['uid']]);
}
+ $last_clean = DI::keyValue()->get('tumblr_last_clean');
+ if (empty($last_clean) || ($last_clean + 86400 < time())) {
+ Logger::notice('Start contact cleanup');
+ $contacts = DBA::select('account-user-view', ['id', 'pid'], ["`network` = ? AND `uid` != ? AND `rel` = ?", Protocol::TUMBLR, 0, Contact::NOTHING]);
+ while ($contact = DBA::fetch($contacts)) {
+ Worker::add(Worker::PRIORITY_LOW, 'MergeContact', $contact['pid'], $contact['id'], 0);
+ }
+ DBA::close($contacts);
+ DI::keyValue()->set('tumblr_last_clean', time());
+ Logger::notice('Contact cleanup done');
+ }
+
Logger::notice('cron_end');
DI::keyValue()->set('tumblr_last_poll', time());
return $post;
}
+/**
+ * Fetch posts for user defined hashtags for the given user
+ *
+ * @param integer $uid
+ * @return void
+ */
+function tumblr_fetch_tags(int $uid)
+{
+ if (!DI::config()->get('tumblr', 'max_tags') ?? TUMBLR_DEFAULT_MAXIMUM_TAGS) {
+ return;
+ }
+
+ foreach (DI::pConfig()->get($uid, 'tumblr', 'tags') ?? [] as $tag) {
+ $data = tumblr_get($uid, 'tagged', ['tag' => $tag]);
+ foreach (array_reverse($data->response) as $post) {
+ $id = tumblr_process_post($post, $uid, Item::PR_TAG);
+ if (!empty($id)) {
+ Logger::debug('Tag post imported', ['tag' => $tag, 'id' => $id]);
+ $post = Post::selectFirst(['uri-id'], ['id' => $id]);
+ $stored = Post\Category::storeFileByURIId($post['uri-id'], $uid, Post\Category::SUBCRIPTION, $tag);
+ Logger::debug('Stored tag subscription for user', ['uri-id' => $post['uri-id'], 'uid' => $uid, 'tag' => $tag, 'stored' => $stored]);
+ }
+ }
+ }
+}
+
/**
* Fetch the dashboard (timeline) for the given user
*
*/
function tumblr_fetch_dashboard(int $uid)
{
- $page = tumblr_get_page($uid);
-
$parameters = ['reblog_info' => false, 'notes_info' => false, 'npf' => false];
$last = DI::pConfig()->get($uid, 'tumblr', 'last_id');
}
foreach (array_reverse($dashboard->response->posts) as $post) {
- $uri = 'tumblr::' . $post->id_string . ':' . $post->reblog_key;
-
if ($post->id > $last) {
$last = $post->id;
}
- Logger::debug('Importing post', ['uid' => $uid, 'created' => date(DateTimeFormat::MYSQL, $post->timestamp), 'uri' => $uri]);
-
- if (Post::exists(['uri' => $uri, 'uid' => $uid]) || ($post->blog->uuid == $page)) {
- DI::pConfig()->set($uid, 'tumblr', 'last_id', $last);
- continue;
- }
-
- tumblr_process_post($post, $uid, $uri);
+ Logger::debug('Importing post', ['uid' => $uid, 'created' => date(DateTimeFormat::MYSQL, $post->timestamp), 'id' => $post->id_string]);
+ tumblr_process_post($post, $uid, Item::PR_NONE);
DI::pConfig()->set($uid, 'tumblr', 'last_id', $last);
}
}
-function tumblr_process_post(stdClass $post, int $uid, string $uri = ''): int
+function tumblr_process_post(stdClass $post, int $uid, int $post_reason): int
{
- if (empty($uri)) {
- $uri = 'tumblr::' . $post->id_string . ':' . $post->reblog_key;
+ $uri = 'tumblr::' . $post->id_string . ':' . $post->reblog_key;
+
+ if (Post::exists(['uri' => $uri, 'uid' => $uid]) || ($post->blog->uuid == tumblr_get_page($uid))) {
+ return 0;
}
$item = tumblr_get_header($post, $uri, $uid);
$item = tumblr_get_content($item, $post);
+ $item['post-reason'] = $post_reason;
+
+ if (!empty($post->followed)) {
+ $item['post-reason'] = Item::PR_FOLLOWER;
+ }
+
$id = item::insert($item);
if ($id) {
*/
function tumblr_get_contact(stdClass $blog, int $uid): array
{
- $condition = ['network' => Protocol::TUMBLR, 'uid' => $uid, 'poll' => 'tumblr::' . $blog->uuid];
- $contact = Contact::selectFirst([], $condition);
- if (!empty($contact) && (strtotime($contact['updated']) >= $blog->updated)) {
- return $contact;
- }
+ $condition = ['network' => Protocol::TUMBLR, 'uid' => 0, 'poll' => 'tumblr::' . $blog->uuid];
+ $contact = Contact::selectFirst(['id', 'updated'], $condition);
+
+ $update = empty($contact) || $contact['updated'] < DateTimeFormat::utc('now -24 hours');
+
+ $public_fields = $fields = tumblr_get_contact_fields($blog, $uid, $update);
+
+ $avatar = $fields['avatar'] ?? '';
+ unset($fields['avatar']);
+ unset($public_fields['avatar']);
+
+ $public_fields['uid'] = 0;
+ $public_fields['rel'] = Contact::NOTHING;
+
if (empty($contact)) {
- $cid = tumblr_insert_contact($blog, $uid);
+ $cid = Contact::insert($public_fields);
} else {
$cid = $contact['id'];
+ Contact::update($public_fields, ['id' => $cid], true);
}
- $condition['uid'] = 0;
+ if ($uid != 0) {
+ $condition = ['network' => Protocol::TUMBLR, 'uid' => $uid, 'poll' => 'tumblr::' . $blog->uuid];
- $contact = Contact::selectFirst([], $condition);
- if (empty($contact)) {
- $pcid = tumblr_insert_contact($blog, 0);
+ $contact = Contact::selectFirst(['id', 'rel', 'uid'], $condition);
+ if (!isset($fields['rel']) && isset($contact['rel'])) {
+ $fields['rel'] = $contact['rel'];
+ } elseif (!isset($fields['rel'])) {
+ $fields['rel'] = Contact::NOTHING;
+ }
+ }
+
+ if (($uid != 0) && ($fields['rel'] != Contact::NOTHING)) {
+ if (empty($contact)) {
+ $cid = Contact::insert($fields);
+ } else {
+ $cid = $contact['id'];
+ Contact::update($fields, ['id' => $cid], true);
+ }
+ Logger::debug('Get user contact', ['id' => $cid, 'uid' => $uid, 'update' => $update]);
} else {
- $pcid = $contact['id'];
+ Logger::debug('Get public contact', ['id' => $cid, 'uid' => $uid, 'update' => $update]);
}
- tumblr_update_contact($blog, $uid, $cid, $pcid);
+ if (!empty($avatar)) {
+ Contact::updateAvatar($cid, $avatar);
+ }
return Contact::getById($cid);
}
-/**
- * Create a new contact
- *
- * @param stdClass $blog
- * @param integer $uid
- * @return void
- */
-function tumblr_insert_contact(stdClass $blog, int $uid)
+function tumblr_get_contact_fields(stdClass $blog, int $uid, bool $update): array
{
$baseurl = 'https://tumblr.com';
$url = $baseurl . '/' . $blog->name;
'url' => $url,
'nurl' => Strings::normaliseLink($url),
'alias' => $blog->url,
- 'name' => $blog->title,
+ 'name' => $blog->title ?: $blog->name,
'nick' => $blog->name,
'addr' => $blog->name . '@tumblr.com',
'about' => HTML::toBBCode($blog->description),
'updated' => date(DateTimeFormat::MYSQL, $blog->updated)
];
- return Contact::insert($fields);
-}
-/**
- * Updates the given contact for the given user and proviced contact ids
- *
- * @param stdClass $blog
- * @param integer $uid
- * @param integer $cid
- * @param integer $pcid
- * @return void
- */
-function tumblr_update_contact(stdClass $blog, int $uid, int $cid, int $pcid)
-{
+ if (!$update) {
+ Logger::debug('Got contact fields', ['uid' => $uid, 'url' => $fields['url']]);
+ return $fields;
+ }
+
$info = tumblr_get($uid, 'blog/' . $blog->uuid . '/info');
if ($info->meta->status > 399) {
- Logger::notice('Error fetching dashboard', ['meta' => $info->meta, 'response' => $info->response, 'errors' => $info->errors]);
- return;
+ Logger::notice('Error fetching blog info', ['meta' => $info->meta, 'response' => $info->response, 'errors' => $info->errors]);
+ return $fields;
}
$avatar = $info->response->blog->avatar;
if (!empty($avatar)) {
- Contact::updateAvatar($cid, $avatar[0]->url);
+ $fields['avatar'] = $avatar[0]->url;
}
- $baseurl = 'https://tumblr.com';
- $url = $baseurl . '/' . $info->response->blog->name;
-
if ($info->response->blog->followed && $info->response->blog->subscribed) {
- $rel = Contact::FRIEND;
+ $fields['rel'] = Contact::FRIEND;
} elseif ($info->response->blog->followed && !$info->response->blog->subscribed) {
- $rel = Contact::SHARING;
+ $fields['rel'] = Contact::SHARING;
} elseif (!$info->response->blog->followed && $info->response->blog->subscribed) {
- $rel = Contact::FOLLOWER;
+ $fields['rel'] = Contact::FOLLOWER;
} else {
- $rel = Contact::NOTHING;
+ $fields['rel'] = Contact::NOTHING;
}
- $uri_id = ItemURI::getIdByURI($url);
- $fields = [
- 'url' => $url,
- 'nurl' => Strings::normaliseLink($url),
- 'uri-id' => $uri_id,
- 'alias' => $info->response->blog->url,
- 'name' => $info->response->blog->title,
- 'nick' => $info->response->blog->name,
- 'addr' => $info->response->blog->name . '@tumblr.com',
- 'about' => HTML::toBBCode($info->response->blog->description),
- 'updated' => date(DateTimeFormat::MYSQL, $info->response->blog->updated),
- 'header' => $info->response->blog->theme->header_image_focused,
- 'rel' => $rel,
- ];
+ $fields['header'] = $info->response->blog->theme->header_image_focused;
- Contact::update($fields, ['id' => $cid]);
-
- $fields['rel'] = Contact::NOTHING;
- Contact::update($fields, ['id' => $pcid]);
+ Logger::debug('Got updated contact fields', ['uid' => $uid, 'url' => $fields['url']]);
+ return $fields;
}
/**
return $blogs;
}
-function tumblr_enabled_for_user(int $uid)
+function tumblr_enabled_for_user(int $uid)
{
return !empty($uid) && !empty(DI::pConfig()->get($uid, 'tumblr', 'access_token')) &&
!empty(DI::pConfig()->get($uid, 'tumblr', 'refresh_token')) &&
* Get a contact array from a Tumblr url
*
* @param string $url
- * @return array
+ * @return array|null
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
-function tumblr_get_contact_by_url(string $url): array
+function tumblr_get_contact_by_url(string $url): ?array
{
$consumer_key = DI::config()->get('tumblr', 'consumer_key');
if (empty($consumer_key)) {
- return [];
+ return null;
}
if (!preg_match('#^https?://tumblr.com/(.+)#', $url, $matches) && !preg_match('#^https?://www\.tumblr.com/(.+)#', $url, $matches) && !preg_match('#^https?://(.+)\.tumblr.com#', $url, $matches)) {
try {
$curlResult = DI::httpClient()->get($url);
} catch (\Exception $e) {
- return [];
+ return null;
}
$html = $curlResult->getBody();
if (empty($html)) {
- return [];
+ return null;
}
$doc = new DOMDocument();
@$doc->loadHTML($html);
}
if (empty($blog)) {
- return [];
+ return null;
}
+ Logger::debug('Update Tumblr blog data', ['url' => $url]);
+
$curlResult = DI::httpClient()->get('https://api.tumblr.com/v2/blog/' . $blog . '/info?api_key=' . $consumer_key);
$body = $curlResult->getBody();
$data = json_decode($body);
if (empty($data)) {
- return [];
+ return null;
+ }
+
+ if (is_array($data->response->blog) || empty($data->response->blog)) {
+ Logger::warning('Unexpected blog format', ['blog' => $blog, 'data' => $data]);
+ return null;
}
$baseurl = 'https://tumblr.com';
'notify' => '',
'poll' => 'tumblr::' . $data->response->blog->uuid,
'poco' => '',
- 'name' => $data->response->blog->title,
+ 'name' => $data->response->blog->title ?: $data->response->blog->name,
'nick' => $data->response->blog->name,
'network' => Protocol::TUMBLR,
'baseurl' => $baseurl,
'priority' => 0,
'guid' => $data->response->blog->uuid,
'about' => HTML::toBBCode($data->response->blog->description),
- 'photo' => $data->response->blog->avatar[0]->url,
- 'header' => $data->response->blog->theme->header_image_focused,
+ 'photo' => $data->response->blog->avatar[0]->url,
+ 'header' => $data->response->blog->theme->header_image_focused,
];
}
Logger::info('Error fetching token', ['uid' => $uid, 'code' => $code, 'result' => $curlResult->getBody(), 'parameters' => $parameters]);
return '';
}
-
+
$result = json_decode($curlResult->getBody());
if (empty($result)) {
Logger::info('Invalid result when updating token', ['uid' => $uid]);
return '';
}
-
+
$expires_at = time() + $result->expires_in;
Logger::debug('Renewed token', ['uid' => $uid, 'expires_at' => date('c', $expires_at)]);
}