Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
153b6b7775 pnut: Import o timeline 2026-03-15 23:12:31 +00:00
2 changed files with 734 additions and 8 deletions

View file

@ -2,7 +2,7 @@
/** /**
* Name: Pnut Connector * Name: Pnut Connector
* Description: Post to pnut.io * Description: Post to pnut.io
* Version: 0.1.2 * Version: 0.2.0
* Author: Morgan McMillian <https://social.clacks.network/profile/spacenerdmo> * Author: Morgan McMillian <https://social.clacks.network/profile/spacenerdmo>
* Status: In Development * Status: In Development
*/ */
@ -11,14 +11,19 @@ require_once 'addon/pnut/lib/phpnut.php';
require_once 'addon/pnut/lib/phpnutException.php'; require_once 'addon/pnut/lib/phpnutException.php';
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Content\Text\Plaintext; use Friendica\Content\Text\Plaintext;
use Friendica\Core\Config\Util\ConfigFileManager; use Friendica\Content\Text\Markdown;
use Friendica\Core\Hook; use Friendica\Core\Hook;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Circle;
use Friendica\Util\Strings;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Photo; use Friendica\Model\Photo;
use Friendica\Core\Protocol;
use phpnut\phpnutException; use phpnut\phpnutException;
const PNUT_LIMIT = 256; const PNUT_LIMIT = 256;
@ -32,6 +37,9 @@ function pnut_install()
Hook::register('jot_networks', __FILE__, 'pnut_jot_nets'); Hook::register('jot_networks', __FILE__, 'pnut_jot_nets');
Hook::register('connector_settings', __FILE__, 'pnut_settings'); Hook::register('connector_settings', __FILE__, 'pnut_settings');
Hook::register('connector_settings_post', __FILE__, 'pnut_settings_post'); Hook::register('connector_settings_post', __FILE__, 'pnut_settings_post');
Hook::register('cron', __FILE__, 'pnut_cron');
Hook::register('prepare_body', __FILE__, 'pnut_prepare_body');
Hook::register('check_item_notification', __FILE__, 'pnut_check_item_notification');
} }
function pnut_module() {} function pnut_module() {}
@ -78,6 +86,10 @@ function pnut_connect()
DI::logger()->debug('Got Token', [$token]); DI::logger()->debug('Got Token', [$token]);
$o = DI::l10n()->t('You are now authenticated with pnut.io.'); $o = DI::l10n()->t('You are now authenticated with pnut.io.');
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'access_token', $token); DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'access_token', $token);
// Get user info and set ownid
$userdata = $nut->getUser();
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'ownid', $userdata['id']);
} catch (phpnutException $e) { } catch (phpnutException $e) {
$o = DI::l10n()->t('Error fetching token. Please try again.', ['code' => $e->getCode(), 'message' => $e->getMessage()]); $o = DI::l10n()->t('Error fetching token. Please try again.', ['code' => $e->getCode(), 'message' => $e->getMessage()]);
} }
@ -87,9 +99,15 @@ function pnut_connect()
return $o; return $o;
} }
function pnut_load_config(ConfigFileManager $loader) function pnut_check_item_notification($a, &$notification_data)
{ {
DI::appHelper()->getConfigCache()->load($loader->loadAddonConfig('pnut'), \Friendica\Core\Config\ValueObject\Cache::SOURCE_STATIC); $own_id = DI::pConfig()->get($notification_data["uid"], 'pnut', 'ownid');
$own_user = DI::dba()->p("SELECT `url` FROM `contact` WHERE `uid` = ? AND `alias` = ?", $notification_data["uid"], "pnut::" . $own_id);
if ($own_user) {
$notification_data["profiles"][] = $own_user[0]["url"];
}
} }
function pnut_addon_admin(string &$o) function pnut_addon_admin(string &$o)
@ -123,9 +141,11 @@ function pnut_settings(array &$data)
$enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'post') ?? false; $enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'post') ?? false;
$def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'post_by_default') ?? false; $def_enabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'post_by_default') ?? false;
$importenabled = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'import') ?? false;
$client_id = DI::config()->get('pnut', 'client_id'); $client_id = DI::config()->get('pnut', 'client_id');
$client_secret = DI::config()->get('pnut', 'client_secret'); $client_secret = DI::config()->get('pnut', 'client_secret');
$token = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'access_token'); $token = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'access_token');
$ownid = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'ownid');
$user_client = empty($client_id) || empty($client_secret); $user_client = empty($client_id) || empty($client_secret);
if ($user_client) { if ($user_client) {
@ -133,6 +153,8 @@ function pnut_settings(array &$data)
$client_secret = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'client_secret'); $client_secret = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'pnut', 'client_secret');
} }
$o = '';
if (!empty($client_id) && !empty($client_secret) && empty($token)) { if (!empty($client_id) && !empty($client_secret) && empty($token)) {
$nut = new phpnut\phpnut($client_id, $client_secret); $nut = new phpnut\phpnut($client_id, $client_secret);
$authorize_url = $nut->getAuthUrl($redirectUri, $scope); $authorize_url = $nut->getAuthUrl($redirectUri, $scope);
@ -140,14 +162,22 @@ function pnut_settings(array &$data)
} }
if (!empty($token)) { if (!empty($token)) {
$disconn_btn = DI::l10n()->t('Disconnect'); $nut = new phpnut\phpnut($token);
try {
$userdata = $nut->getUser();
if ($ownid != $userdata["id"]) {
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'ownid', $userdata["id"]);
}
$o .= '<div id="pnut-info" ><img id="pnut-avatar" src="' . $userdata["avatar_image"]["url"] . '" /><p id="pnut-info-block">' . DI::l10n()->t('Currently connected to: ') . '<a href="' . $userdata["canonical_url"] . '" target="_pnut">' . $userdata["username"] . '</a><br /><em>' . $userdata["description"]["text"] . '</em></p></div>';
} catch (phpnutException $e) {
$o .= DI::l10n()->t("<p>Error fetching user profile. Please clear the configuration and try again.</p>");
}
} }
$t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/pnut/'); $t = Renderer::getMarkupTemplate('connector_settings.tpl', 'addon/pnut/');
$html = Renderer::replaceMacros($t, [ $html = Renderer::replaceMacros($t, [
'$enable' => ['pnut', DI::l10n()->t('Enable Pnut Post Addon'), $enabled], '$enable' => ['pnut', DI::l10n()->t('Enable Pnut Post Addon'), $enabled],
'$bydefault' => ['pnut_bydefault', DI::l10n()->t('Post to Pnut by default'), $def_enabled], '$bydefault' => ['pnut_bydefault', DI::l10n()->t('Post to Pnut by default'), $def_enabled], '$import' => ['pnut_import', DI::l10n()->t('Import the remote timeline'), $importenabled], '$client_id' => ['pnut_client_id', DI::l10n()->t('Client ID'), $client_id],
'$client_id' => ['pnut_client_id', DI::l10n()->t('Client ID'), $client_id],
'$client_secret' => ['pnut_client_secret', DI::l10n()->t('Client Secret'), $client_secret], '$client_secret' => ['pnut_client_secret', DI::l10n()->t('Client Secret'), $client_secret],
'$access_token' => ['pnut_access_token', DI::l10n()->t('Access Token'), $token, '', '', 'readonly'], '$access_token' => ['pnut_access_token', DI::l10n()->t('Access Token'), $token, '', '', 'readonly'],
'$authorize_url' => $authorize_url ?? '', '$authorize_url' => $authorize_url ?? '',
@ -174,12 +204,14 @@ function pnut_settings_post(array &$b)
if (!empty($_POST['pnut-disconnect'])) { if (!empty($_POST['pnut-disconnect'])) {
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'post'); DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'post');
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'post_by_default'); DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'post_by_default');
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'import');
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'client_id'); DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'client_id');
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'client_secret'); DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'client_secret');
DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'access_token'); DI::pConfig()->delete(DI::userSession()->getLocalUserId(), 'pnut', 'access_token');
} else { } else {
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'post', intval($_POST['pnut'])); DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'post', intval($_POST['pnut']));
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'post_by_default', intval($_POST['pnut_bydefault'])); DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'post_by_default', intval($_POST['pnut_bydefault']));
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'import', intval($_POST['pnut_import']));
if (!empty($_POST['pnut_client_id'])) { if (!empty($_POST['pnut_client_id'])) {
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'client_id', $_POST['pnut_client_id']); DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'pnut', 'client_id', $_POST['pnut_client_id']);
} }
@ -275,6 +307,59 @@ function pnut_post_hook(array &$b)
$token = DI::pConfig()->get($b['uid'], 'pnut', 'access_token'); $token = DI::pConfig()->get($b['uid'], 'pnut', 'access_token');
$nut = new phpnut\phpnut($token); $nut = new phpnut\phpnut($token);
$iscomment = false;
$islike = false;
if ($b['parent'] != $b['id']) {
// Looking if its a reply to a pnut post
if ((substr($b["parent-uri"], 0, 6) != "pnut::") && (substr($b["extid"], 0, 6) != "pnut::") && (substr($b["thr-parent"], 0, 6) != "pnut::")) {
DI::logger()->debug("PNUT post: no pnut post " . $b["parent"]);
return;
}
$r = DI::dba()->p("SELECT * FROM item WHERE item.uri = ? AND item.uid = ? LIMIT 1", $b["thr-parent"], intval($b["uid"]));
if (!count($r)) {
DI::logger()->debug("PNUT post: no parent found " . $b["thr-parent"]);
return;
} else {
$iscomment = true;
$orig_post = $r[0];
}
$nicknameplain = preg_replace("=https?://pnut.io/(.*)=ism", "$1", $orig_post["author-link"]);
$nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
$nicknameplain = "@" . $nicknameplain;
DI::logger()->debug('PNUT post: comparing ' . $nickname . ' and ' . $nicknameplain . ' with ' . $b["body"]);
if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
$b["body"] = $nickname . " " . $b["body"];
}
DI::logger()->debug('PNUT post: parent found ' . print_r($orig_post, true));
} elseif ($b['verb'] == 'http://activitystrea.ms/schema/1.0/like') {
DI::logger()->debug('PNUT post: like detected');
$islike = true;
}
if (($b['verb'] == 'http://activitystrea.ms/schema/1.0/post') && $b['deleted']) {
pnut_action($b["uid"], substr($orig_post["uri"], 6), "delete");
return;
}
if ($b['verb'] == 'http://activitystrea.ms/schema/1.0/like') {
if ($b['deleted']) {
pnut_action($b["uid"], substr($b["thr-parent"], 6), "unlike");
} else {
pnut_action($b["uid"], substr($b["thr-parent"], 6), "like");
}
return;
}
if ($b['deleted'] || ($b['created'] !== $b['edited'])) {
return;
}
$msgarr = Plaintext::getPost($b, PNUT_LIMIT, true, BBCode::EXTERNAL); $msgarr = Plaintext::getPost($b, PNUT_LIMIT, true, BBCode::EXTERNAL);
$text = $msgarr['text']; $text = $msgarr['text'];
$raw = []; $raw = [];
@ -319,7 +404,647 @@ function pnut_post_hook(array &$b)
} }
$raw['io.pnut.core.crosspost'][] = ['canonical_url' => $b['plink']]; $raw['io.pnut.core.crosspost'][] = ['canonical_url' => $b['plink']];
$nut->createPost($text, ['raw' => $raw]);
$data = ['raw' => $raw];
if ($iscomment) {
$data['reply_to'] = substr($orig_post["uri"], 6);
}
$nut->createPost($text, $data);
DI::logger()->debug('PNUT post complete', ['id' => $b['id'], 'text' => $text, 'raw' => $raw]); DI::logger()->debug('PNUT post complete', ['id' => $b['id'], 'text' => $text, 'raw' => $raw]);
} }
function pnut_cron($a, $b)
{
$last = DI::config()->get('pnut', 'last_poll');
$poll_interval = intval(DI::config()->get('pnut', 'poll_interval'));
if (!$poll_interval) {
$poll_interval = 5; // default 5 minutes
}
if ($last) {
$next = $last + ($poll_interval * 60);
if ($next > time()) {
DI::logger()->debug('PNUT cron: poll interval not reached');
return;
}
}
DI::logger()->debug('PNUT cron: cron_start');
$abandon_days = intval(DI::config()->get('system', 'account_abandon_days', 0));
if ($abandon_days < 1) {
$abandon_days = 0;
}
$abandon_limit = date("Y-m-d H:i:s", time() - $abandon_days * 86400);
$r = DI::dba()->p("SELECT * FROM `pconfig` WHERE `cat` = 'pnut' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
if (count($r)) {
foreach ($r as $rr) {
if ($abandon_days != 0) {
$user = DI::dba()->p("SELECT `login_date` FROM `user` WHERE uid = ? AND `login_date` >= ?", $rr['uid'], $abandon_limit);
if (!count($user)) {
DI::logger()->debug('abandoned account: timeline from user ' . $rr['uid'] . ' will not be imported');
continue;
}
}
DI::logger()->debug('PNUT cron: importing timeline from user ' . $rr['uid']);
pnut_fetchstream($rr["uid"]);
}
}
DI::logger()->debug('PNUT cron: cron_end');
DI::config()->set('pnut', 'last_poll', time());
}
function pnut_fetchstream($uid)
{
$token = DI::pConfig()->get($uid, 'pnut', 'access_token');
$nut = new phpnut\phpnut($token);
$r = DI::dba()->p("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = ? LIMIT 1", intval($uid));
if (count($r)) {
$me = $r[0];
} else {
DI::logger()->debug("PNUT fetchstream: Own contact not found for user " . $uid);
return;
}
$user = DI::dba()->p("SELECT * FROM `user` WHERE `uid` = ? AND `account_expired` = 0 LIMIT 1", intval($uid));
if (count($user)) {
$user = $user[0];
} else {
DI::logger()->debug("PNUT fetchstream: Own user not found for user " . $uid);
return;
}
$ownid = DI::pConfig()->get($uid, 'pnut', 'ownid');
// Fetch stream
$param = array("count" => 200, "include_deleted" => false, "include_directed_posts" => true,
"include_html" => false, "include_post_annotations" => true);
$lastid = DI::pConfig()->get($uid, 'pnut', 'laststreamid');
if ($lastid <> "") {
$param["since_id"] = $lastid;
}
try {
$stream = $nut->getUserStream($param);
} catch (phpnutException $e) {
DI::logger()->debug("PNUT fetchstream: Error fetching stream for user " . $uid . " " . $e->getMessage());
return;
}
if (!is_array($stream)) {
$stream = array();
}
$stream = array_reverse($stream);
foreach ($stream as $post) {
$postarray = pnut_createpost($uid, $post, $me, $user, $ownid, true);
$item = Item::insert($postarray);
$postarray["id"] = $item;
DI::logger()->debug('PNUT fetchstream: User ' . $uid . ' posted stream item ' . $item);
$lastid = $post["id"];
}
DI::pConfig()->set($uid, 'pnut', 'laststreamid', $lastid);
// Fetch mentions
$param = array("count" => 200, "include_deleted" => false, "include_directed_posts" => true,
"include_html" => false, "include_post_annotations" => true);
$lastid = DI::pConfig()->get($uid, 'pnut', 'lastmentionid');
if ($lastid <> "") {
$param["since_id"] = $lastid;
}
try {
$mentions = $nut->getUserMentions("me", $param);
} catch (phpnutException $e) {
DI::logger()->debug("PNUT fetchstream: Error fetching mentions for user " . $uid . " " . $e->getMessage());
return;
}
if (!is_array($mentions)) {
$mentions = array();
}
$mentions = array_reverse($mentions);
foreach ($mentions as $post) {
$postarray = pnut_createpost($uid, $post, $me, $user, $ownid, false);
if (isset($postarray["id"])) {
$item = $postarray["id"];
$parent_id = $postarray['parent'];
} elseif (isset($postarray["body"])) {
$item = Item::insert($postarray);
$postarray["id"] = $item;
$parent_id = 0;
DI::logger()->debug('PNUT fetchstream: User ' . $uid . ' posted mention item ' . $item);
} else {
$item = 0;
$parent_id = 0;
}
// Fetch the parent and id
if (($parent_id == 0) && ($postarray['uri'] != "")) {
$r = DI::dba()->p("SELECT `id`, `parent` FROM `item` WHERE `uri` = ? AND `uid` = ? LIMIT 1", $postarray['uri'], intval($uid));
if (count($r)) {
$item = $r[0]['id'];
$parent_id = $r[0]['parent'];
}
}
$lastid = $post["id"];
}
DI::pConfig()->set($uid, 'pnut', 'lastmentionid', $lastid);
}
function pnut_createpost($uid, $post, $me, $user, $ownid, $createuser, $threadcompletion = true, $nodupcheck = false)
{
if ($post["machine_only"]) {
return;
}
if ($post["is_deleted"]) {
return;
}
$postarray = array();
$postarray['gravity'] = 0;
$postarray['uid'] = $uid;
$postarray['wall'] = 0;
$postarray['verb'] = 'http://activitystrea.ms/schema/1.0/post';
$postarray['network'] = Protocol::PNUT; // Assuming Protocol::PNUT is defined, else use a string
if (is_array($post["repost_of"])) {
// You can't reply to reposts. So use the original id and thread-id
$postarray['uri'] = "pnut::" . $post["repost_of"]["id"];
$postarray['parent-uri'] = "pnut::" . $post["repost_of"]["thread_id"];
} else {
$postarray['uri'] = "pnut::" . $post["id"];
$postarray['parent-uri'] = "pnut::" . $post["thread_id"];
}
if (!$nodupcheck) {
$r = DI::dba()->p("SELECT * FROM `item` WHERE `uri` = ? AND `uid` = ? LIMIT 1", $postarray['uri'], intval($uid));
if (count($r)) {
return $r[0];
}
$r = DI::dba()->p("SELECT * FROM `item` WHERE `extid` = ? AND `uid` = ? LIMIT 1", $postarray['uri'], intval($uid));
if (count($r)) {
return $r[0];
}
}
if (isset($post["reply_to"]) && ($post["reply_to"] != "")) {
$postarray['thr-parent'] = "pnut::" . $post["reply_to"];
// Complete the thread (if the parent doesn't exists)
if ($threadcompletion) {
DI::logger()->debug("PNUT createpost: completing thread " . $post["thread_id"] . " for user " . $uid);
$token = DI::pConfig()->get($uid, 'pnut', 'access_token');
$nut = new phpnut\phpnut($token);
$param = array("count" => 200, "include_deleted" => false, "include_directed_posts" => true,
"include_html" => false, "include_post_annotations" => true);
try {
$thread = [];
} catch (phpnutException $e) {
DI::logger()->debug("PNUT createpost: Error fetching thread for user " . $uid . " " . $e->getMessage());
}
$thread = array_reverse($thread);
DI::logger()->debug("PNUT createpost: fetched " . count($thread) . " items for thread " . $post["thread_id"] . " for user " . $uid);
foreach ($thread as $tpost) {
$threadpost = pnut_createpost($uid, $tpost, $me, $user, $ownid, false, false);
$item = Item::insert($threadpost);
$threadpost["id"] = $item;
DI::logger()->debug("PNUT createpost: stored post " . $post["id"] . " thread " . $post["thread_id"] . " in item " . $item);
}
}
// Don't create accounts of people who just comment something
$createuser = false;
$postarray['object-type'] = 'http://activitystrea.ms/schema/1.0/comment';
} else {
$postarray['thr-parent'] = $postarray['uri'];
$postarray['object-type'] = 'http://activitystrea.ms/schema/1.0/note';
}
if (($post["user"]["id"] != $ownid) || ($postarray['thr-parent'] == $postarray['uri'])) {
$postarray['owner-name'] = $post["user"]["name"];
$postarray['owner-link'] = $post["user"]["canonical_url"];
$postarray['owner-avatar'] = $post["user"]["avatar_image"]["url"];
$postarray['contact-id'] = pnut_fetchcontact($uid, $post["user"], $me, $createuser);
} else {
$postarray['owner-name'] = $me["name"];
$postarray['owner-link'] = $me["url"];
$postarray['owner-avatar'] = $me["thumb"];
$postarray['contact-id'] = $me["id"];
}
if (is_array($post["repost_of"])) {
$postarray['author-name'] = $post["repost_of"]["user"]["name"];
$postarray['author-link'] = $post["repost_of"]["user"]["canonical_url"];
$postarray['author-avatar'] = $post["repost_of"]["user"]["avatar_image"]["url"];
$content = $post["repost_of"];
} else {
$postarray['author-name'] = $postarray['owner-name'];
$postarray['author-link'] = $postarray['owner-link'];
$postarray['author-avatar'] = $postarray['owner-avatar'];
$content = $post;
}
$postarray['plink'] = $content["canonical_url"];
if (is_array($content["entities"])) {
$converted = pnut_expand_entities($content["text"], $content["entities"]);
$postarray['body'] = $converted["body"];
$postarray['tag'] = $converted["tags"];
} else {
$postarray['body'] = $content["text"];
}
if (sizeof($content["entities"]["links"])) {
foreach ($content["entities"]["links"] as $link) {
$url = Strings::normaliseLink($link["url"]);
$links[$url] = $link["url"];
}
}
$page_info = "";
if (is_array($content["annotations"])) {
$photo = pnut_expand_annotations($content["annotations"]);
if (($photo["large"] != "") && ($photo["url"] != "")) {
$page_info = "\n[url=" . $photo["url"] . "][img]" . $photo["large"] . "[/img][/url]";
} elseif ($photo["url"] != "") {
$page_info = "\n[img]" . $photo["url"] . "[/img]";
}
if ($photo["url"] != "") {
$postarray['object-type'] = 'http://activitystrea.ms/schema/1.0/image';
}
} else {
$photo = array("url" => "", "large" => "");
}
if (sizeof($links)) {
$link = array_pop($links);
$url = str_replace(array('/', '.'), array('\/', '\.'), $link);
$page_info = "";
if (trim($page_info) != "") {
$removedlink = preg_replace("/\[url\=" . $url . "\](.*?)\[\/url\]/ism", '', $postarray['body']);
if (($removedlink == "") || strstr($postarray['body'], $removedlink)) {
$postarray['body'] = $removedlink;
}
}
}
$postarray['body'] .= $page_info;
$postarray['created'] = (new DateTime($post["created_at"]))->format(DateTime::ATOM);
$postarray['edited'] = (new DateTime($post["created_at"]))->format(DateTime::ATOM);
$postarray['app'] = $post["source"]["name"];
return $postarray;
}
function pnut_expand_entities($body, $entities)
{
if (!function_exists('substr_unicode')) {
function substr_unicode($str, $s, $l = null) {
return join("", array_slice(
preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY), $s, $l));
}
}
$tags_arr = array();
$replace = array();
foreach ($entities["mentions"] as $mention) {
$url = "@[url=https://pnut.io/" . rawurlencode($mention["name"]) . "]" . $mention["name"] . "[/url]";
$tags_arr["@" . $mention["name"]] = $url;
$replace[$mention["pos"]] = array("pos" => $mention["pos"], "len" => $mention["len"], "replace" => $url);
}
foreach ($entities["hashtags"] as $hashtag) {
$url = "#[url=" . DI::baseUrl() . "/search?tag=" . rawurlencode($hashtag["name"]) . "]" . $hashtag["name"] . "[/url]";
$tags_arr["#" . $hashtag["name"]] = $url;
$replace[$hashtag["pos"]] = array("pos" => $hashtag["pos"], "len" => $hashtag["len"], "replace" => $url);
}
foreach ($entities["links"] as $links) {
$url = "[url=" . $links["url"] . "]" . $links["text"] . "[/url]";
if (isset($links["amended_len"]) && ($links["amended_len"] > $links["len"])) {
$replace[$links["pos"]] = array("pos" => $links["pos"], "len" => $links["amended_len"], "replace" => $url);
} else {
$replace[$links["pos"]] = array("pos" => $links["pos"], "len" => $links["len"], "replace" => $url);
}
}
if (sizeof($replace)) {
krsort($replace);
foreach ($replace as $entity) {
$pre = substr_unicode($body, 0, $entity["pos"]);
$post = substr_unicode($body, $entity["pos"] + $entity["len"]);
$body = $pre . $entity["replace"] . $post;
}
}
return array("body" => $body, "tags" => implode(",", $tags_arr));
}
function pnut_expand_annotations($annotations)
{
$photo = array("url" => "", "large" => "");
foreach ($annotations as $annotation) {
if (($annotation["type"] == "io.pnut.core.oembed") &&
($annotation["value"]["type"] == "photo")) {
if ($annotation["value"]["url"] != "") {
$photo["url"] = $annotation["value"]["url"];
}
if ($annotation["value"]["thumbnail_large_url"] != "") {
$photo["large"] = $annotation["value"]["thumbnail_large_url"];
}
}
}
return $photo;
}
function pnut_action($uid, $post_id, $action)
{
// TODO: Implement pnut action API call
DI::logger()->info('PNUT action', ['uid' => $uid, 'post_id' => $post_id, 'action' => $action]);
}
function pnut_fetchcontact($uid, $contact, $me, $create_user)
{
$r = DI::dba()->p("SELECT * FROM `contact` WHERE `uid` = ? AND `alias` = ? LIMIT 1", intval($uid), "pnut::" . $contact["id"]);
if (!count($r) && !$create_user) {
return $me["id"];
}
if ($contact["canonical_url"] == "") {
return $me["id"];
}
if (count($r) && ($r[0]["readonly"] || $r[0]["blocked"])) {
DI::logger()->debug("PNUT fetchcontact: Contact '" . $r[0]["nick"] . "' is blocked or readonly.");
return -1;
}
if (!count($r)) {
if ($contact["name"] == "") {
$contact["name"] = $contact["username"];
}
if ($contact["username"] == "") {
$contact["username"] = $contact["name"];
}
// create contact record
DI::dba()->p("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
`name`, `nick`, `photo`, `network`, `rel`, `priority`,
`about`, `writable`, `blocked`, `readonly`, `pending` )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0 )",
intval($uid),
new DateTime('now', new DateTimeZone('UTC')),
$contact["canonical_url"],
Strings::normaliseLink($contact["canonical_url"]),
$contact["username"] . "@pnut.io",
"pnut::" . $contact["id"],
'',
"pnut::" . $contact["id"],
$contact["name"],
$contact["username"],
$contact["avatar_image"]["url"],
Protocol::PNUT,
intval(Contact::SHARING),
intval(1),
$contact["description"]["text"],
intval(1)
);
$r = DI::dba()->p("SELECT * FROM `contact` WHERE `alias` = ? AND `uid` = ? LIMIT 1",
"pnut::" . $contact["id"], intval($uid));
if (!count($r)) {
return false;
}
$contact_id = $r[0]['id'];
$g = DI::dba()->p("SELECT def_gid FROM user WHERE uid = ? LIMIT 1", intval($uid));
if ($g && intval($g[0]['def_gid'])) {
require_once('include/group.php');
Circle::addMember($uid, '', $contact_id, $g[0]['def_gid']);
}
$photos = Photo::importProfilePhoto($contact["avatar_image"]["url"], $uid, $contact_id);
DI::dba()->p("UPDATE `contact` SET `photo` = ?, `thumb` = ?, `micro` = ?,
`name-date` = ?, `uri-date` = ?, `avatar-date` = ? WHERE `id` = ?",
$photos[0],
$photos[1],
$photos[2],
new DateTime('now', new DateTimeZone('UTC')),
new DateTime('now', new DateTimeZone('UTC')),
new DateTime('now', new DateTimeZone('UTC')),
intval($contact_id)
);
} else {
// update profile photos once every two weeks as we have no notification of when they change.
$update_photo = ($r[0]['avatar-date'] < (new DateTime('now', new DateTimeZone('UTC')))->modify('-12 hours'));
// check that we have all the photos, this has been known to fail on occasion
if ((!$r[0]['photo']) || (!$r[0]['thumb']) || (!$r[0]['micro']) || ($update_photo)) {
DI::logger()->debug("PNUT fetchcontact: Updating contact " . $contact["username"]);
$photos = Photo::importProfilePhoto($contact["avatar_image"]["url"], $uid, $r[0]['id']);
DI::dba()->p("UPDATE `contact` SET `photo` = ?, `thumb` = ?, `micro` = ?,
`name-date` = ?, `uri-date` = ?, `avatar-date` = ?,
`url` = ?, `nurl` = ?, `addr` = ?, `name` = ?, `nick` = ?, `about` = ? WHERE `id` = ?",
$photos[0],
$photos[1],
$photos[2],
new DateTime('now', new DateTimeZone('UTC')),
new DateTime('now', new DateTimeZone('UTC')),
new DateTime('now', new DateTimeZone('UTC')),
$contact["canonical_url"],
Strings::normaliseLink($contact["canonical_url"]),
$contact["username"] . "@pnut.io",
$contact["name"],
$contact["username"],
$contact["description"]["text"],
intval($r[0]['id'])
);
}
}
return $r[0]["id"];
}
function pnut_prepare_body($a, &$b)
{
if ($b["item"]["network"] != Protocol::PNUT) {
return;
}
if ($b["preview"]) {
$max_char = 256;
require_once("include/plaintext.php");
$item = $b["item"];
$item["plink"] = DI::baseUrl() . "/display/" . $a->user["nickname"] . "/" . $item["parent"];
$r = DI::dba()->p("SELECT `author-link` FROM item WHERE item.uri = ? AND item.uid = ? LIMIT 1",
$item["thr-parent"], intval(DI::userSession()->getLocalUserId()));
if (count($r)) {
$orig_post = $r[0];
$nicknameplain = preg_replace("=https?://pnut.io/(.*)=ism", "$1", $orig_post["author-link"]);
$nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
$nicknameplain = "@" . $nicknameplain;
if ((strpos($item["body"], $nickname) === false) && (strpos($item["body"], $nicknameplain) === false)) {
$item["body"] = $nickname . " " . $item["body"];
}
}
$msgarr = Plaintext::getPost($a, $item, $max_char, true);
$msg = pnut_create_entities($a, $item, $msgarr);
$msg = Markdown::convert($msg);
$b['html'] = $msg;
}
}
function pnut_create_entities($a, $b, $postdata)
{
require_once("include/bbcode.php");
require_once("include/plaintext.php");
$bbcode = $b["body"];
$bbcode = BBCode::removeShareInformation($bbcode, false, true);
// Change pure links in text to bbcode uris
$bbcode = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2]$2[/url]', $bbcode);
$URLSearchString = "^\[\]";
$bbcode = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",'#$2',$bbcode);
$bbcode = preg_replace("/@\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",'@$2',$bbcode);
$bbcode = preg_replace("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",'[url=$1]$2[/url]',$bbcode);
$bbcode = preg_replace("/\[video\](.*?)\[\/video\]/ism",'[url=$1]$1[/url]',$bbcode);
$bbcode = preg_replace("/\[youtube\]https?:\/\/(.*?)\[\/youtube\]/ism",'[url=https://$1]https://$1[/url]',$bbcode);
$bbcode = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism",
'[url=https://www.youtube.com/watch?v=$1]https://www.youtube.com/watch?v=$1[/url]', $bbcode);
$bbcode = preg_replace("/\[vimeo\]https?:\/\/(.*?)\[\/vimeo\]/ism",'[url=https://$1]https://$1[/url]',$bbcode);
$bbcode = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism",
'[url=https://vimeo.com/$1]https://vimeo.com/$1[/url]', $bbcode);
$bbcode = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $bbcode);
preg_match_all("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $bbcode, $urls, PREG_SET_ORDER);
$bbcode = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",'$1',$bbcode);
$b["body"] = $bbcode;
$plaintext = Plaintext::getPost($a, $b, 0, false, 6);
$text = $plaintext["text"];
$start = 0;
$entities = array();
foreach ($urls as $url) {
$lenurl = iconv_strlen($url[1], "UTF-8");
$len = iconv_strlen($url[2], "UTF-8");
$pos = iconv_strpos($text, $url[1], $start, "UTF-8");
$pre = iconv_substr($text, 0, $pos, "UTF-8");
$post = iconv_substr($text, $pos + $lenurl, 1000000, "UTF-8");
$mid = $url[2];
$html = BBCode::convert($mid, false, false, 6);
$mid = HTML::toPlaintext($html, 0, true);
$mid = trim(html_entity_decode($mid, ENT_QUOTES, 'UTF-8'));
$text = $pre . $mid . $post;
if ($mid != "") {
$entities[] = array("pos" => $pos, "len" => $len, "url" => $url[1], "text" => $mid);
}
$start = $pos + 1;
}
if (isset($postdata["url"]) && isset($postdata["title"]) && ($postdata["type"] != "photo")) {
$postdata["title"] = Plaintext::shorten($postdata["title"], 90);
$max = 256 - strlen($postdata["title"]);
$text = Plaintext::shorten($text, $max);
$text .= "\n[" . $postdata["title"] . "](" . $postdata["url"] . ")";
} elseif (isset($postdata["url"]) && ($postdata["type"] != "photo")) {
$postdata["url"] = Strings::normaliseLink($postdata["url"]);
$max = 240;
$text = Plaintext::shorten($text, $max);
$text .= " [" . $postdata["url"] . "](" . $postdata["url"] . ")";
} else {
$max = 256;
$text = Plaintext::shorten($text, $max);
}
if (iconv_strlen($text, "UTF-8") < $max) {
$max = iconv_strlen($text, "UTF-8");
}
krsort($entities);
foreach ($entities as $entity) {
if (($entity["pos"] + $entity["len"]) <= $max) {
$pre = iconv_substr($text, 0, $entity["pos"], "UTF-8");
$post = iconv_substr($text, $entity["pos"] + $entity["len"], 1000000, "UTF-8");
$text = $pre . "[" . $entity["text"] . "](" . $entity["url"] . ")" . $post;
}
}
return $text;
}

View file

@ -1,6 +1,7 @@
<p>{{$status}}</p> <p>{{$status}}</p>
{{include file="field_checkbox.tpl" field=$enable}} {{include file="field_checkbox.tpl" field=$enable}}
{{include file="field_checkbox.tpl" field=$bydefault}} {{include file="field_checkbox.tpl" field=$bydefault}}
{{include file="field_checkbox.tpl" field=$import}}
{{if $user_client}} {{if $user_client}}
{{include file="field_input.tpl" field=$client_id}} {{include file="field_input.tpl" field=$client_id}}
{{include file="field_input.tpl" field=$client_secret}} {{include file="field_input.tpl" field=$client_secret}}