4 * Name: GNU Social Connector
5 * Description: Bidirectional (posting, relaying and reading) connector for GNU Social.
7 * Author: Tobias Diekershoff <https://f.diekershoff.de/profile/tobias>
8 * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
10 * Copyright (c) 2011-2013 Tobias Diekershoff, Michael Vogel
11 * All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 * * Redistributions of source code must retain the above copyright notice,
16 * this list of conditions and the following disclaimer.
17 * * Redistributions in binary form must reproduce the above
18 * * copyright notice, this list of conditions and the following disclaimer in
19 * the documentation and/or other materials provided with the distribution.
20 * * Neither the name of the <organization> nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
26 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
28 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
32 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 define('STATUSNET_DEFAULT_POLL_INTERVAL', 5); // given in minutes
38 require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'statusnetoauth.php';
39 require_once 'include/enotify.php';
41 use CodebirdSN\CodebirdSN;
43 use Friendica\Content\OEmbed;
44 use Friendica\Content\Text\HTML;
45 use Friendica\Content\Text\Plaintext;
46 use Friendica\Core\Addon;
47 use Friendica\Core\Config;
48 use Friendica\Core\L10n;
49 use Friendica\Core\PConfig;
50 use Friendica\Core\Protocol;
51 use Friendica\Database\DBA;
52 use Friendica\Model\Contact;
53 use Friendica\Model\GContact;
54 use Friendica\Model\Group;
55 use Friendica\Model\Item;
56 use Friendica\Model\ItemContent;
57 use Friendica\Model\Photo;
58 use Friendica\Model\User;
59 use Friendica\Util\DateTimeFormat;
60 use Friendica\Util\Network;
62 function statusnet_install()
64 // we need some hooks, for the configuration and for sending tweets
65 Addon::registerHook('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
66 Addon::registerHook('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
67 Addon::registerHook('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
68 Addon::registerHook('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
69 Addon::registerHook('jot_networks', 'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
70 Addon::registerHook('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
71 Addon::registerHook('prepare_body', 'addon/statusnet/statusnet.php', 'statusnet_prepare_body');
72 Addon::registerHook('check_item_notification', 'addon/statusnet/statusnet.php', 'statusnet_check_item_notification');
73 logger("installed GNU Social");
76 function statusnet_uninstall()
78 Addon::unregisterHook('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
79 Addon::unregisterHook('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
80 Addon::unregisterHook('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
81 Addon::unregisterHook('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
82 Addon::unregisterHook('jot_networks', 'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
83 Addon::unregisterHook('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
84 Addon::unregisterHook('prepare_body', 'addon/statusnet/statusnet.php', 'statusnet_prepare_body');
85 Addon::unregisterHook('check_item_notification', 'addon/statusnet/statusnet.php', 'statusnet_check_item_notification');
87 // old setting - remove only
88 Addon::unregisterHook('post_local_end', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
89 Addon::unregisterHook('addon_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
90 Addon::unregisterHook('addon_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
93 function statusnet_check_item_notification(App $a, &$notification_data)
95 if (PConfig::get($notification_data["uid"], 'statusnet', 'post')) {
96 $notification_data["profiles"][] = PConfig::get($notification_data["uid"], 'statusnet', 'own_url');
100 function statusnet_jot_nets(App $a, &$b)
106 $statusnet_post = PConfig::get(local_user(), 'statusnet', 'post');
107 if (intval($statusnet_post) == 1) {
108 $statusnet_defpost = PConfig::get(local_user(), 'statusnet', 'post_by_default');
109 $selected = ((intval($statusnet_defpost) == 1) ? ' checked="checked" ' : '');
110 $b .= '<div class="profile-jot-net"><input type="checkbox" name="statusnet_enable"' . $selected . ' value="1" /> '
111 . L10n::t('Post to GNU Social') . '</div>';
115 function statusnet_settings_post(App $a, $post)
120 // don't check GNU Social settings if GNU Social submit button is not clicked
121 if (!x($_POST, 'statusnet-submit')) {
125 if (isset($_POST['statusnet-disconnect'])) {
127 * if the GNU Social-disconnect checkbox is set, clear the GNU Social configuration
129 PConfig::delete(local_user(), 'statusnet', 'consumerkey');
130 PConfig::delete(local_user(), 'statusnet', 'consumersecret');
131 PConfig::delete(local_user(), 'statusnet', 'post');
132 PConfig::delete(local_user(), 'statusnet', 'post_by_default');
133 PConfig::delete(local_user(), 'statusnet', 'oauthtoken');
134 PConfig::delete(local_user(), 'statusnet', 'oauthsecret');
135 PConfig::delete(local_user(), 'statusnet', 'baseapi');
136 PConfig::delete(local_user(), 'statusnet', 'lastid');
137 PConfig::delete(local_user(), 'statusnet', 'mirror_posts');
138 PConfig::delete(local_user(), 'statusnet', 'import');
139 PConfig::delete(local_user(), 'statusnet', 'create_user');
140 PConfig::delete(local_user(), 'statusnet', 'own_url');
142 if (isset($_POST['statusnet-preconf-apiurl'])) {
144 * If the user used one of the preconfigured GNU Social server credentials
145 * use them. All the data are available in the global config.
146 * Check the API Url never the less and blame the admin if it's not working ^^
148 $globalsn = Config::get('statusnet', 'sites');
149 foreach ($globalsn as $asn) {
150 if ($asn['apiurl'] == $_POST['statusnet-preconf-apiurl']) {
151 $apibase = $asn['apiurl'];
152 $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
153 if (strlen($c) > 0) {
154 PConfig::set(local_user(), 'statusnet', 'consumerkey', $asn['consumerkey']);
155 PConfig::set(local_user(), 'statusnet', 'consumersecret', $asn['consumersecret']);
156 PConfig::set(local_user(), 'statusnet', 'baseapi', $asn['apiurl']);
157 //PConfig::set(local_user(), 'statusnet', 'application_name', $asn['applicationname'] );
159 notice(L10n::t('Please contact your site administrator.<br />The provided API URL is not valid.') . EOL . $asn['apiurl'] . EOL);
163 goaway('settings/connectors');
165 if (isset($_POST['statusnet-consumersecret'])) {
166 // check if we can reach the API of the GNU Social server
167 // we'll check the API Version for that, if we don't get one we'll try to fix the path but will
168 // resign quickly after this one try to fix the path ;-)
169 $apibase = $_POST['statusnet-baseapi'];
170 $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
171 if (strlen($c) > 0) {
172 // ok the API path is correct, let's save the settings
173 PConfig::set(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
174 PConfig::set(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
175 PConfig::set(local_user(), 'statusnet', 'baseapi', $apibase);
176 //PConfig::set(local_user(), 'statusnet', 'application_name', $_POST['statusnet-applicationname'] );
178 // the API path is not correct, maybe missing trailing / ?
179 $apibase = $apibase . '/';
180 $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
181 if (strlen($c) > 0) {
182 // ok the API path is now correct, let's save the settings
183 PConfig::set(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
184 PConfig::set(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
185 PConfig::set(local_user(), 'statusnet', 'baseapi', $apibase);
187 // still not the correct API base, let's do noting
188 notice(L10n::t('We could not contact the GNU Social API with the Path you entered.') . EOL);
191 goaway('settings/connectors');
193 if (isset($_POST['statusnet-pin'])) {
194 // if the user supplied us with a PIN from GNU Social, let the magic of OAuth happen
195 $api = PConfig::get(local_user(), 'statusnet', 'baseapi');
196 $ckey = PConfig::get(local_user(), 'statusnet', 'consumerkey');
197 $csecret = PConfig::get(local_user(), 'statusnet', 'consumersecret');
198 // the token and secret for which the PIN was generated were hidden in the settings
199 // form as token and token2, we need a new connection to GNU Social using these token
200 // and secret to request a Access Token with the PIN
201 $connection = new StatusNetOAuth($api, $ckey, $csecret, $_POST['statusnet-token'], $_POST['statusnet-token2']);
202 $token = $connection->getAccessToken($_POST['statusnet-pin']);
203 // ok, now that we have the Access Token, save them in the user config
204 PConfig::set(local_user(), 'statusnet', 'oauthtoken', $token['oauth_token']);
205 PConfig::set(local_user(), 'statusnet', 'oauthsecret', $token['oauth_token_secret']);
206 PConfig::set(local_user(), 'statusnet', 'post', 1);
207 PConfig::set(local_user(), 'statusnet', 'post_taglinks', 1);
208 // reload the Addon Settings page, if we don't do it see Bug #42
209 goaway('settings/connectors');
211 // if no PIN is supplied in the POST variables, the user has changed the setting
212 // to post a dent for every new __public__ posting to the wall
213 PConfig::set(local_user(), 'statusnet', 'post', intval($_POST['statusnet-enable']));
214 PConfig::set(local_user(), 'statusnet', 'post_by_default', intval($_POST['statusnet-default']));
215 PConfig::set(local_user(), 'statusnet', 'mirror_posts', intval($_POST['statusnet-mirror']));
216 PConfig::set(local_user(), 'statusnet', 'import', intval($_POST['statusnet-import']));
217 PConfig::set(local_user(), 'statusnet', 'create_user', intval($_POST['statusnet-create_user']));
219 if (!intval($_POST['statusnet-mirror']))
220 PConfig::delete(local_user(), 'statusnet', 'lastid');
222 info(L10n::t('GNU Social settings updated.') . EOL);
229 function statusnet_settings(App $a, &$s)
234 $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->getBaseURL() . '/addon/statusnet/statusnet.css' . '" media="all" />' . "\r\n";
236 * 1) Check that we have a base api url and a consumer key & secret
237 * 2) If no OAuthtoken & stuff is present, generate button to get some
238 * allow the user to cancel the connection process at this step
239 * 3) Checkbox for "Send public notices (respect size limitation)
241 $api = PConfig::get(local_user(), 'statusnet', 'baseapi');
242 $ckey = PConfig::get(local_user(), 'statusnet', 'consumerkey');
243 $csecret = PConfig::get(local_user(), 'statusnet', 'consumersecret');
244 $otoken = PConfig::get(local_user(), 'statusnet', 'oauthtoken');
245 $osecret = PConfig::get(local_user(), 'statusnet', 'oauthsecret');
246 $enabled = PConfig::get(local_user(), 'statusnet', 'post');
247 $checked = (($enabled) ? ' checked="checked" ' : '');
248 $defenabled = PConfig::get(local_user(), 'statusnet', 'post_by_default');
249 $defchecked = (($defenabled) ? ' checked="checked" ' : '');
250 $mirrorenabled = PConfig::get(local_user(), 'statusnet', 'mirror_posts');
251 $mirrorchecked = (($mirrorenabled) ? ' checked="checked" ' : '');
252 $import = PConfig::get(local_user(), 'statusnet', 'import');
253 $importselected = ["", "", ""];
254 $importselected[$import] = ' selected="selected"';
255 //$importenabled = PConfig::get(local_user(),'statusnet','import');
256 //$importchecked = (($importenabled) ? ' checked="checked" ' : '');
257 $create_userenabled = PConfig::get(local_user(), 'statusnet', 'create_user');
258 $create_userchecked = (($create_userenabled) ? ' checked="checked" ' : '');
260 $css = (($enabled) ? '' : '-disabled');
262 $s .= '<span id="settings_statusnet_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_statusnet_expanded\'); openClose(\'settings_statusnet_inflated\');">';
263 $s .= '<img class="connector' . $css . '" src="images/gnusocial.png" /><h3 class="connector">' . L10n::t('GNU Social Import/Export/Mirror') . '</h3>';
265 $s .= '<div id="settings_statusnet_expanded" class="settings-block" style="display: none;">';
266 $s .= '<span class="fakelink" onclick="openClose(\'settings_statusnet_expanded\'); openClose(\'settings_statusnet_inflated\');">';
267 $s .= '<img class="connector' . $css . '" src="images/gnusocial.png" /><h3 class="connector">' . L10n::t('GNU Social Import/Export/Mirror') . '</h3>';
270 if ((!$ckey) && (!$csecret)) {
274 $globalsn = Config::get('statusnet', 'sites');
276 * lets check if we have one or more globally configured GNU Social
277 * server OAuth credentials in the configuration. If so offer them
278 * with a little explanation to the user as choice - otherwise
279 * ignore this option entirely.
281 if (!$globalsn == null) {
282 $s .= '<h4>' . L10n::t('Globally Available GNU Social OAuthKeys') . '</h4>';
283 $s .= '<p>' . L10n::t("There are preconfigured OAuth key pairs for some GNU Social servers available. If you are using one of them, please use these credentials. If not feel free to connect to any other GNU Social instance \x28see below\x29.") . '</p>';
284 $s .= '<div id="statusnet-preconf-wrapper">';
285 foreach ($globalsn as $asn) {
286 $s .= '<input type="radio" name="statusnet-preconf-apiurl" value="' . $asn['apiurl'] . '">' . $asn['sitename'] . '<br />';
288 $s .= '<p></p><div class="clear"></div></div>';
289 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
291 $s .= '<h4>' . L10n::t('Provide your own OAuth Credentials') . '</h4>';
292 $s .= '<p>' . L10n::t('No consumer key pair for GNU Social found. Register your Friendica Account as an desktop client on your GNU Social account, copy the consumer key pair here and enter the API base root.<br />Before you register your own OAuth key pair ask the administrator if there is already a key pair for this Friendica installation at your favorited GNU Social installation.') . '</p>';
293 $s .= '<div id="statusnet-consumer-wrapper">';
294 $s .= '<label id="statusnet-consumerkey-label" for="statusnet-consumerkey">' . L10n::t('OAuth Consumer Key') . '</label>';
295 $s .= '<input id="statusnet-consumerkey" type="text" name="statusnet-consumerkey" size="35" /><br />';
296 $s .= '<div class="clear"></div>';
297 $s .= '<label id="statusnet-consumersecret-label" for="statusnet-consumersecret">' . L10n::t('OAuth Consumer Secret') . '</label>';
298 $s .= '<input id="statusnet-consumersecret" type="text" name="statusnet-consumersecret" size="35" /><br />';
299 $s .= '<div class="clear"></div>';
300 $s .= '<label id="statusnet-baseapi-label" for="statusnet-baseapi">' . L10n::t("Base API Path \x28remember the trailing /\x29") . '</label>';
301 $s .= '<input id="statusnet-baseapi" type="text" name="statusnet-baseapi" size="35" /><br />';
302 $s .= '<div class="clear"></div>';
303 //$s .= '<label id="statusnet-applicationname-label" for="statusnet-applicationname">'.L10n::t('GNU Socialapplication name').'</label>';
304 //$s .= '<input id="statusnet-applicationname" type="text" name="statusnet-applicationname" size="35" /><br />';
305 $s .= '<p></p><div class="clear"></div>';
306 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
310 * ok we have a consumer key pair now look into the OAuth stuff
312 if ((!$otoken) && (!$osecret)) {
314 * the user has not yet connected the account to GNU Social
315 * get a temporary OAuth key/secret pair and display a button with
316 * which the user can request a PIN to connect the account to a
317 * account at GNU Social
319 $connection = new StatusNetOAuth($api, $ckey, $csecret);
320 $request_token = $connection->getRequestToken('oob');
321 $token = $request_token['oauth_token'];
323 * make some nice form
325 $s .= '<p>' . L10n::t('To connect to your GNU Social account click the button below to get a security code from GNU Social which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to GNU Social.') . '</p>';
326 $s .= '<a href="' . $connection->getAuthorizeURL($token, False) . '" target="_statusnet"><img src="addon/statusnet/signinwithstatusnet.png" alt="' . L10n::t('Log in with GNU Social') . '"></a>';
327 $s .= '<div id="statusnet-pin-wrapper">';
328 $s .= '<label id="statusnet-pin-label" for="statusnet-pin">' . L10n::t('Copy the security code from GNU Social here') . '</label>';
329 $s .= '<input id="statusnet-pin" type="text" name="statusnet-pin" />';
330 $s .= '<input id="statusnet-token" type="hidden" name="statusnet-token" value="' . $token . '" />';
331 $s .= '<input id="statusnet-token2" type="hidden" name="statusnet-token2" value="' . $request_token['oauth_token_secret'] . '" />';
332 $s .= '</div><div class="clear"></div>';
333 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
334 $s .= '<h4>' . L10n::t('Cancel Connection Process') . '</h4>';
335 $s .= '<div id="statusnet-cancel-wrapper">';
336 $s .= '<p>' . L10n::t('Current GNU Social API is') . ': ' . $api . '</p>';
337 $s .= '<label id="statusnet-cancel-label" for="statusnet-cancel">' . L10n::t('Cancel GNU Social Connection') . '</label>';
338 $s .= '<input id="statusnet-cancel" type="checkbox" name="statusnet-disconnect" value="1" />';
339 $s .= '</div><div class="clear"></div>';
340 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
343 * we have an OAuth key / secret pair for the user
344 * so let's give a chance to disable the postings to GNU Social
346 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
347 $details = $connection->get('account/verify_credentials');
348 if (!empty($details)) {
349 $s .= '<div id="statusnet-info" ><img id="statusnet-avatar" src="' . $details->profile_image_url . '" /><p id="statusnet-info-block">' . L10n::t('Currently connected to: ') . '<a href="' . $details->statusnet_profile_url . '" target="_statusnet">' . $details->screen_name . '</a><br /><em>' . $details->description . '</em></p></div>';
351 $s .= '<p>' . L10n::t('If enabled all your <strong>public</strong> postings can be posted to the associated GNU Social account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.') . '</p>';
352 if ($a->user['hidewall']) {
353 $s .= '<p>' . L10n::t('<strong>Note</strong>: Due your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to GNU Social will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') . '</p>';
355 $s .= '<div id="statusnet-enable-wrapper">';
356 $s .= '<label id="statusnet-enable-label" for="statusnet-checkbox">' . L10n::t('Allow posting to GNU Social') . '</label>';
357 $s .= '<input id="statusnet-checkbox" type="checkbox" name="statusnet-enable" value="1" ' . $checked . '/>';
358 $s .= '<div class="clear"></div>';
359 $s .= '<label id="statusnet-default-label" for="statusnet-default">' . L10n::t('Send public postings to GNU Social by default') . '</label>';
360 $s .= '<input id="statusnet-default" type="checkbox" name="statusnet-default" value="1" ' . $defchecked . '/>';
361 $s .= '<div class="clear"></div>';
363 $s .= '<label id="statusnet-mirror-label" for="statusnet-mirror">' . L10n::t('Mirror all posts from GNU Social that are no replies or repeated messages') . '</label>';
364 $s .= '<input id="statusnet-mirror" type="checkbox" name="statusnet-mirror" value="1" ' . $mirrorchecked . '/>';
366 $s .= '<div class="clear"></div>';
369 $s .= '<label id="statusnet-import-label" for="statusnet-import">' . L10n::t('Import the remote timeline') . '</label>';
370 //$s .= '<input id="statusnet-import" type="checkbox" name="statusnet-import" value="1" '. $importchecked . '/>';
372 $s .= '<select name="statusnet-import" id="statusnet-import" />';
373 $s .= '<option value="0" ' . $importselected[0] . '>' . L10n::t("Disabled") . '</option>';
374 $s .= '<option value="1" ' . $importselected[1] . '>' . L10n::t("Full Timeline") . '</option>';
375 $s .= '<option value="2" ' . $importselected[2] . '>' . L10n::t("Only Mentions") . '</option>';
377 $s .= '<div class="clear"></div>';
379 $s .= '<label id="statusnet-create_user-label" for="statusnet-create_user">'.L10n::t('Automatically create contacts').'</label>';
380 $s .= '<input id="statusnet-create_user" type="checkbox" name="statusnet-create_user" value="1" '. $create_userchecked . '/>';
381 $s .= '<div class="clear"></div>';
383 $s .= '<div id="statusnet-disconnect-wrapper">';
384 $s .= '<label id="statusnet-disconnect-label" for="statusnet-disconnect">' . L10n::t('Clear OAuth configuration') . '</label>';
385 $s .= '<input id="statusnet-disconnect" type="checkbox" name="statusnet-disconnect" value="1" />';
386 $s .= '</div><div class="clear"></div>';
387 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
390 $s .= '</div><div class="clear"></div>';
393 function statusnet_post_local(App $a, &$b)
399 if (!local_user() || (local_user() != $b['uid'])) {
403 $statusnet_post = PConfig::get(local_user(), 'statusnet', 'post');
404 $statusnet_enable = (($statusnet_post && x($_REQUEST, 'statusnet_enable')) ? intval($_REQUEST['statusnet_enable']) : 0);
406 // if API is used, default to the chosen settings
407 if ($b['api_source'] && intval(PConfig::get(local_user(), 'statusnet', 'post_by_default'))) {
408 $statusnet_enable = 1;
411 if (!$statusnet_enable) {
415 if (strlen($b['postopts'])) {
416 $b['postopts'] .= ',';
419 $b['postopts'] .= 'statusnet';
422 function statusnet_action(App $a, $uid, $pid, $action)
424 $api = PConfig::get($uid, 'statusnet', 'baseapi');
425 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
426 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
427 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
428 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
430 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
432 logger("statusnet_action '" . $action . "' ID: " . $pid, LOGGER_DATA);
436 $result = $connection->post("statuses/destroy/" . $pid);
439 $result = $connection->post("favorites/create/" . $pid);
442 $result = $connection->post("favorites/destroy/" . $pid);
445 logger("statusnet_action '" . $action . "' send, result: " . print_r($result, true), LOGGER_DEBUG);
448 function statusnet_post_hook(App $a, &$b)
453 if (!PConfig::get($b["uid"], 'statusnet', 'import')) {
454 if ($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))
458 $api = PConfig::get($b["uid"], 'statusnet', 'baseapi');
459 $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
461 if ($b['parent'] != $b['id']) {
462 logger("statusnet_post_hook: parameter " . print_r($b, true), LOGGER_DATA);
464 // Looking if its a reply to a GNU Social post
465 $hostlength = strlen($hostname) + 2;
466 if ((substr($b["parent-uri"], 0, $hostlength) != $hostname . "::") && (substr($b["extid"], 0, $hostlength) != $hostname . "::") && (substr($b["thr-parent"], 0, $hostlength) != $hostname . "::")) {
467 logger("statusnet_post_hook: no GNU Social post " . $b["parent"]);
471 $condition = ['uri' => $b["thr-parent"], 'uid' => $b["uid"]];
472 $orig_post = Item::selectFirst(['author-link', 'uri'], $condition);
473 if (!DBA::isResult($orig_post)) {
474 logger("statusnet_post_hook: no parent found " . $b["thr-parent"]);
480 $nick = preg_replace("=https?://(.*)/(.*)=ism", "$2", $orig_post["author-link"]);
482 $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nick . "[/url]";
483 $nicknameplain = "@" . $nick;
485 logger("statusnet_post_hook: comparing " . $nickname . " and " . $nicknameplain . " with " . $b["body"], LOGGER_DEBUG);
486 if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
487 $b["body"] = $nickname . " " . $b["body"];
490 logger("statusnet_post_hook: parent found " . print_r($orig_post, true), LOGGER_DEBUG);
494 if ($b['private'] || !strstr($b['postopts'], 'statusnet')) {
498 // Dont't post if the post doesn't belong to us.
499 // This is a check for forum postings
500 $self = DBA::selectFirst('contact', ['id'], ['uid' => $b['uid'], 'self' => true]);
501 if ($b['contact-id'] != $self['id']) {
506 if (($b['verb'] == ACTIVITY_POST) && $b['deleted']) {
507 statusnet_action($a, $b["uid"], substr($orig_post["uri"], $hostlength), "delete");
510 if ($b['verb'] == ACTIVITY_LIKE) {
511 logger("statusnet_post_hook: parameter 2 " . substr($b["thr-parent"], $hostlength), LOGGER_DEBUG);
513 statusnet_action($a, $b["uid"], substr($b["thr-parent"], $hostlength), "unlike");
515 statusnet_action($a, $b["uid"], substr($b["thr-parent"], $hostlength), "like");
519 if ($b['deleted'] || ($b['created'] !== $b['edited'])) {
523 // if posts comes from GNU Social don't send it back
524 if ($b['extid'] == Protocol::STATUSNET) {
528 if ($b['app'] == "StatusNet") {
532 logger('GNU Socialpost invoked');
534 PConfig::load($b['uid'], 'statusnet');
536 $api = PConfig::get($b['uid'], 'statusnet', 'baseapi');
537 $ckey = PConfig::get($b['uid'], 'statusnet', 'consumerkey');
538 $csecret = PConfig::get($b['uid'], 'statusnet', 'consumersecret');
539 $otoken = PConfig::get($b['uid'], 'statusnet', 'oauthtoken');
540 $osecret = PConfig::get($b['uid'], 'statusnet', 'oauthsecret');
542 if ($ckey && $csecret && $otoken && $osecret) {
543 // If it's a repeated message from GNU Social then do a native retweet and exit
544 if (statusnet_is_retweet($a, $b['uid'], $b['body'])) {
548 $dent = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
549 $max_char = $dent->get_maxlength(); // max. length for a dent
551 PConfig::set($b['uid'], 'statusnet', 'max_char', $max_char);
554 $msgarr = ItemContent::getPlaintextPost($b, $max_char, true, 7);
555 $msg = $msgarr["text"];
557 if (($msg == "") && isset($msgarr["title"]))
558 $msg = Plaintext::shorten($msgarr["title"], $max_char - 50);
562 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
563 $msg .= " \n" . $msgarr["url"];
564 } elseif (isset($msgarr["image"]) && ($msgarr["type"] != "video")) {
565 $image = $msgarr["image"];
569 $img_str = Network::fetchUrl($image);
570 $tempfile = tempnam(get_temppath(), "cache");
571 file_put_contents($tempfile, $img_str);
572 $postdata = ["status" => $msg, "media[]" => $tempfile];
574 $postdata = ["status" => $msg];
577 // and now send it :-)
580 $postdata["in_reply_to_status_id"] = substr($orig_post["uri"], $hostlength);
581 logger('statusnet_post send reply ' . print_r($postdata, true), LOGGER_DEBUG);
584 // New code that is able to post pictures
585 require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'codebirdsn.php';
586 $cb = CodebirdSN::getInstance();
587 $cb->setAPIEndpoint($api);
588 $cb->setConsumerKey($ckey, $csecret);
589 $cb->setToken($otoken, $osecret);
590 $result = $cb->statuses_update($postdata);
591 //$result = $dent->post('statuses/update', $postdata);
592 logger('statusnet_post send, result: ' . print_r($result, true) .
593 "\nmessage: " . $msg . "\nOriginal post: " . print_r($b, true) . "\nPost Data: " . print_r($postdata, true), LOGGER_DEBUG);
595 if (!empty($result->source)) {
596 PConfig::set($b["uid"], "statusnet", "application_name", strip_tags($result->source));
599 if (!empty($result->error)) {
600 logger('Send to GNU Social failed: "' . $result->error . '"');
601 } elseif ($iscomment) {
602 logger('statusnet_post: Update extid ' . $result->id . " for post id " . $b['id']);
603 Item::update(['extid' => $hostname . "::" . $result->id, 'body' => $result->text], ['id' => $b['id']]);
606 if ($tempfile != "") {
612 function statusnet_addon_admin_post(App $a)
616 foreach ($_POST['sitename'] as $id => $sitename) {
617 $sitename = trim($sitename);
618 $apiurl = trim($_POST['apiurl'][$id]);
619 if (!(substr($apiurl, -1) == '/')) {
620 $apiurl = $apiurl . '/';
622 $secret = trim($_POST['secret'][$id]);
623 $key = trim($_POST['key'][$id]);
624 //$applicationname = ((x($_POST, 'applicationname')) ? notags(trim($_POST['applicationname'][$id])):'');
625 if ($sitename != "" &&
629 !x($_POST['delete'][$id])) {
632 'sitename' => $sitename,
634 'consumersecret' => $secret,
635 'consumerkey' => $key,
636 //'applicationname' => $applicationname
641 $sites = Config::set('statusnet', 'sites', $sites);
644 function statusnet_addon_admin(App $a, &$o)
646 $sites = Config::get('statusnet', 'sites');
648 if (is_array($sites)) {
649 foreach ($sites as $id => $s) {
651 'sitename' => ["sitename[$id]", "Site name", $s['sitename'], ""],
652 'apiurl' => ["apiurl[$id]", "Api url", $s['apiurl'], L10n::t("Base API Path \x28remember the trailing /\x29")],
653 'secret' => ["secret[$id]", "Secret", $s['consumersecret'], ""],
654 'key' => ["key[$id]", "Key", $s['consumerkey'], ""],
655 //'applicationname' => Array("applicationname[$id]", "Application name", $s['applicationname'], ""),
656 'delete' => ["delete[$id]", "Delete", False, "Check to delete this preset"],
660 /* empty form to add new site */
663 'sitename' => ["sitename[$id]", L10n::t("Site name"), "", ""],
664 'apiurl' => ["apiurl[$id]", "Api url", "", L10n::t("Base API Path \x28remember the trailing /\x29")],
665 'secret' => ["secret[$id]", L10n::t("Consumer Secret"), "", ""],
666 'key' => ["key[$id]", L10n::t("Consumer Key"), "", ""],
667 //'applicationname' => Array("applicationname[$id]", L10n::t("Application name"), "", ""),
670 $t = get_markup_template("admin.tpl", "addon/statusnet/");
671 $o = replace_macros($t, [
672 '$submit' => L10n::t('Save Settings'),
673 '$sites' => $sitesform,
677 function statusnet_prepare_body(App $a, &$b)
679 if ($b["item"]["network"] != Protocol::STATUSNET) {
684 $max_char = PConfig::get(local_user(), 'statusnet', 'max_char');
685 if (intval($max_char) == 0) {
690 $item["plink"] = $a->getBaseURL() . "/display/" . $a->user["nickname"] . "/" . $item["parent"];
692 $condition = ['uri' => $item["thr-parent"], 'uid' => local_user()];
693 $orig_post = Item::selectFirst(['author-link', 'uri'], $condition);
694 if (DBA::isResult($orig_post)) {
695 $nick = preg_replace("=https?://(.*)/(.*)=ism", "$2", $orig_post["author-link"]);
697 $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nick . "[/url]";
698 $nicknameplain = "@" . $nick;
700 if ((strpos($item["body"], $nickname) === false) && (strpos($item["body"], $nicknameplain) === false)) {
701 $item["body"] = $nickname . " " . $item["body"];
705 $msgarr = ItemContent::getPlaintextPost($item, $max_char, true, 7);
706 $msg = $msgarr["text"];
708 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
709 $msg .= " " . $msgarr["url"];
712 if (isset($msgarr["image"])) {
713 $msg .= " " . $msgarr["image"];
716 $b['html'] = nl2br(htmlspecialchars($msg));
720 function statusnet_cron(App $a, $b)
722 $last = Config::get('statusnet', 'last_poll');
724 $poll_interval = intval(Config::get('statusnet', 'poll_interval'));
725 if (!$poll_interval) {
726 $poll_interval = STATUSNET_DEFAULT_POLL_INTERVAL;
730 $next = $last + ($poll_interval * 60);
731 if ($next > time()) {
732 logger('statusnet: poll intervall not reached');
736 logger('statusnet: cron_start');
738 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'statusnet' AND `k` = 'mirror_posts' AND `v` = '1' ORDER BY RAND() ");
739 if (DBA::isResult($r)) {
740 foreach ($r as $rr) {
741 logger('statusnet: fetching for user ' . $rr['uid']);
742 statusnet_fetchtimeline($a, $rr['uid']);
746 $abandon_days = intval(Config::get('system', 'account_abandon_days'));
747 if ($abandon_days < 1) {
751 $abandon_limit = date(DateTimeFormat::MYSQL, time() - $abandon_days * 86400);
753 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'statusnet' AND `k` = 'import' AND `v` ORDER BY RAND()");
754 if (DBA::isResult($r)) {
755 foreach ($r as $rr) {
756 if ($abandon_days != 0) {
757 $user = q("SELECT `login_date` FROM `user` WHERE uid=%d AND `login_date` >= '%s'", $rr['uid'], $abandon_limit);
758 if (!DBA::isResult($user)) {
759 logger('abandoned account: timeline from user ' . $rr['uid'] . ' will not be imported');
764 logger('statusnet: importing timeline from user ' . $rr['uid']);
765 statusnet_fetchhometimeline($a, $rr["uid"], $rr["v"]);
769 logger('statusnet: cron_end');
771 Config::set('statusnet', 'last_poll', time());
774 function statusnet_fetchtimeline(App $a, $uid)
776 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
777 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
778 $api = PConfig::get($uid, 'statusnet', 'baseapi');
779 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
780 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
781 $lastid = PConfig::get($uid, 'statusnet', 'lastid');
783 require_once 'mod/item.php';
784 require_once 'include/items.php';
786 // get the application name for the SN app
787 // 1st try personal config, then system config and fallback to the
788 // hostname of the node if neither one is set.
789 $application_name = PConfig::get($uid, 'statusnet', 'application_name');
790 if ($application_name == "") {
791 $application_name = Config::get('statusnet', 'application_name');
793 if ($application_name == "") {
794 $application_name = $a->getHostName();
797 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
799 $parameters = ["exclude_replies" => true, "trim_user" => true, "contributor_details" => false, "include_rts" => false];
801 $first_time = ($lastid == "");
804 $parameters["since_id"] = $lastid;
807 $items = $connection->get('statuses/user_timeline', $parameters);
809 if (!is_array($items)) {
813 $posts = array_reverse($items);
816 foreach ($posts as $post) {
817 if ($post->id > $lastid)
824 if ($post->source == "activity") {
828 if (!empty($post->retweeted_status)) {
832 if ($post->in_reply_to_status_id != "") {
836 if (!stristr($post->source, $application_name)) {
837 $_SESSION["authenticated"] = true;
838 $_SESSION["uid"] = $uid;
841 $_REQUEST["api_source"] = true;
842 $_REQUEST["profile_uid"] = $uid;
843 //$_REQUEST["source"] = "StatusNet";
844 $_REQUEST["source"] = $post->source;
845 $_REQUEST["extid"] = Protocol::STATUSNET;
847 if (isset($post->id)) {
848 $_REQUEST['message_id'] = Item::newURI($uid, Protocol::STATUSNET . ":" . $post->id);
851 //$_REQUEST["date"] = $post->created_at;
853 $_REQUEST["title"] = "";
855 $_REQUEST["body"] = add_page_info_to_body($post->text, true);
856 if (is_string($post->place->name)) {
857 $_REQUEST["location"] = $post->place->name;
860 if (is_string($post->place->full_name)) {
861 $_REQUEST["location"] = $post->place->full_name;
864 if (is_array($post->geo->coordinates)) {
865 $_REQUEST["coord"] = $post->geo->coordinates[0] . " " . $post->geo->coordinates[1];
868 if (is_array($post->coordinates->coordinates)) {
869 $_REQUEST["coord"] = $post->coordinates->coordinates[1] . " " . $post->coordinates->coordinates[0];
872 //print_r($_REQUEST);
873 if ($_REQUEST["body"] != "") {
874 logger('statusnet: posting for user ' . $uid);
881 PConfig::set($uid, 'statusnet', 'lastid', $lastid);
884 function statusnet_address($contact)
886 $hostname = normalise_link($contact->statusnet_profile_url);
887 $nickname = $contact->screen_name;
889 $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $contact->statusnet_profile_url);
891 $address = $contact->screen_name . "@" . $hostname;
896 function statusnet_fetch_contact($uid, $contact, $create_user)
898 if ($contact->statusnet_profile_url == "") {
902 GContact::update(["url" => $contact->statusnet_profile_url,
903 "network" => Protocol::STATUSNET, "photo" => $contact->profile_image_url,
904 "name" => $contact->name, "nick" => $contact->screen_name,
905 "location" => $contact->location, "about" => $contact->description,
906 "addr" => statusnet_address($contact), "generation" => 3]);
908 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' AND `network` = '%s'LIMIT 1", intval($uid), DBA::escape(normalise_link($contact->statusnet_profile_url)), DBA::escape(Protocol::STATUSNET));
910 if (!DBA::isResult($r) && !$create_user) {
914 if (DBA::isResult($r) && ($r[0]["readonly"] || $r[0]["blocked"])) {
915 logger("statusnet_fetch_contact: Contact '" . $r[0]["nick"] . "' is blocked or readonly.", LOGGER_DEBUG);
919 if (!DBA::isResult($r)) {
920 // create contact record
921 q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
922 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
923 `location`, `about`, `writable`, `blocked`, `readonly`, `pending` )
924 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, 0, 0, 0 ) ",
926 DBA::escape(DateTimeFormat::utcNow()),
927 DBA::escape($contact->statusnet_profile_url),
928 DBA::escape(normalise_link($contact->statusnet_profile_url)),
929 DBA::escape(statusnet_address($contact)),
930 DBA::escape(normalise_link($contact->statusnet_profile_url)),
933 DBA::escape($contact->name),
934 DBA::escape($contact->screen_name),
935 DBA::escape($contact->profile_image_url),
936 DBA::escape(Protocol::STATUSNET),
937 intval(Contact::FRIEND),
939 DBA::escape($contact->location),
940 DBA::escape($contact->description),
944 $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d AND `network` = '%s' LIMIT 1",
945 DBA::escape($contact->statusnet_profile_url),
947 DBA::escape(Protocol::STATUSNET));
949 if (!DBA::isResult($r)) {
953 $contact_id = $r[0]['id'];
955 Group::addMember(User::getDefaultGroup($uid), $contact_id);
957 $photos = Photo::importProfilePhoto($contact->profile_image_url, $uid, $contact_id);
959 q("UPDATE `contact` SET `photo` = '%s',
964 DBA::escape($photos[0]),
965 DBA::escape($photos[1]),
966 DBA::escape($photos[2]),
967 DBA::escape(DateTimeFormat::utcNow()),
971 // update profile photos once every two weeks as we have no notification of when they change.
972 //$update_photo = (($r[0]['avatar-date'] < DateTimeFormat::convert('now -2 days', '', '', )) ? true : false);
973 $update_photo = ($r[0]['avatar-date'] < DateTimeFormat::utc('now -12 hours'));
975 // check that we have all the photos, this has been known to fail on occasion
976 if ((!$r[0]['photo']) || (!$r[0]['thumb']) || (!$r[0]['micro']) || ($update_photo)) {
977 logger("statusnet_fetch_contact: Updating contact " . $contact->screen_name, LOGGER_DEBUG);
979 $photos = Photo::importProfilePhoto($contact->profile_image_url, $uid, $r[0]['id']);
981 q("UPDATE `contact` SET `photo` = '%s',
986 `avatar-date` = '%s',
995 DBA::escape($photos[0]),
996 DBA::escape($photos[1]),
997 DBA::escape($photos[2]),
998 DBA::escape(DateTimeFormat::utcNow()),
999 DBA::escape(DateTimeFormat::utcNow()),
1000 DBA::escape(DateTimeFormat::utcNow()),
1001 DBA::escape($contact->statusnet_profile_url),
1002 DBA::escape(normalise_link($contact->statusnet_profile_url)),
1003 DBA::escape(statusnet_address($contact)),
1004 DBA::escape($contact->name),
1005 DBA::escape($contact->screen_name),
1006 DBA::escape($contact->location),
1007 DBA::escape($contact->description),
1016 function statusnet_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
1018 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1019 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1020 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1021 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1022 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1024 require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'codebirdsn.php';
1026 $cb = CodebirdSN::getInstance();
1027 $cb->setConsumerKey($ckey, $csecret);
1028 $cb->setToken($otoken, $osecret);
1030 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1033 if (DBA::isResult($r)) {
1041 if ($screen_name != "") {
1042 $parameters["screen_name"] = $screen_name;
1045 if ($user_id != "") {
1046 $parameters["user_id"] = $user_id;
1049 // Fetching user data
1050 $user = $cb->users_show($parameters);
1052 if (!is_object($user)) {
1056 $contact_id = statusnet_fetch_contact($uid, $user, true);
1061 function statusnet_createpost(App $a, $uid, $post, $self, $create_user, $only_existing_contact)
1063 logger("statusnet_createpost: start", LOGGER_DEBUG);
1065 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1066 $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1069 $postarray['network'] = Protocol::STATUSNET;
1070 $postarray['uid'] = $uid;
1071 $postarray['wall'] = 0;
1073 if (!empty($post->retweeted_status)) {
1074 $content = $post->retweeted_status;
1075 statusnet_fetch_contact($uid, $content->user, false);
1080 $postarray['uri'] = $hostname . "::" . $content->id;
1082 if (Item::exists(['extid' => $postarray['uri'], 'uid' => $uid])) {
1088 if (!empty($content->in_reply_to_status_id)) {
1090 $parent = $hostname . "::" . $content->in_reply_to_status_id;
1092 $fields = ['uri', 'parent-uri', 'parent'];
1093 $item = Item::selectFirst($fields, ['uri' => $parent, 'uid' => $uid]);
1095 if (!DBA::isResult($item)) {
1096 $item = Item::selectFirst($fields, ['extid' => $parent, 'uid' => $uid]);
1099 if (DBA::isResult($item)) {
1100 $postarray['thr-parent'] = $item['uri'];
1101 $postarray['parent-uri'] = $item['parent-uri'];
1102 $postarray['parent'] = $item['parent'];
1103 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1105 $postarray['thr-parent'] = $postarray['uri'];
1106 $postarray['parent-uri'] = $postarray['uri'];
1107 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1111 $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1113 if ($content->user->id == $own_url) {
1114 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1117 if (DBA::isResult($r)) {
1118 $contactid = $r[0]["id"];
1120 $postarray['owner-name'] = $r[0]["name"];
1121 $postarray['owner-link'] = $r[0]["url"];
1122 $postarray['owner-avatar'] = $r[0]["photo"];
1127 // Don't create accounts of people who just comment something
1128 $create_user = false;
1130 $postarray['parent-uri'] = $postarray['uri'];
1131 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1134 if ($contactid == 0) {
1135 $contactid = statusnet_fetch_contact($uid, $post->user, $create_user);
1136 $postarray['owner-name'] = $post->user->name;
1137 $postarray['owner-link'] = $post->user->statusnet_profile_url;
1138 $postarray['owner-avatar'] = $post->user->profile_image_url;
1140 if (($contactid == 0) && !$only_existing_contact) {
1141 $contactid = $self['id'];
1142 } elseif ($contactid <= 0) {
1146 $postarray['contact-id'] = $contactid;
1148 $postarray['verb'] = ACTIVITY_POST;
1150 $postarray['author-name'] = $content->user->name;
1151 $postarray['author-link'] = $content->user->statusnet_profile_url;
1152 $postarray['author-avatar'] = $content->user->profile_image_url;
1154 // To-Do: Maybe unreliable? Can the api be entered without trailing "/"?
1155 $hostname = str_replace("/api/", "/notice/", PConfig::get($uid, 'statusnet', 'baseapi'));
1157 $postarray['plink'] = $hostname . $content->id;
1158 $postarray['app'] = strip_tags($content->source);
1160 if ($content->user->protected) {
1161 $postarray['private'] = 1;
1162 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1165 $postarray['body'] = HTML::toBBCode($content->statusnet_html);
1167 $converted = statusnet_convertmsg($a, $postarray['body'], false);
1168 $postarray['body'] = $converted["body"];
1169 $postarray['tag'] = $converted["tags"];
1171 $postarray['created'] = DateTimeFormat::utc($content->created_at);
1172 $postarray['edited'] = DateTimeFormat::utc($content->created_at);
1174 if (!empty($content->place->name)) {
1175 $postarray["location"] = $content->place->name;
1178 if (!empty($content->place->full_name)) {
1179 $postarray["location"] = $content->place->full_name;
1182 if (!empty($content->geo->coordinates)) {
1183 $postarray["coord"] = $content->geo->coordinates[0] . " " . $content->geo->coordinates[1];
1186 if (!empty($content->coordinates->coordinates)) {
1187 $postarray["coord"] = $content->coordinates->coordinates[1] . " " . $content->coordinates->coordinates[0];
1190 logger("statusnet_createpost: end", LOGGER_DEBUG);
1195 function statusnet_fetchhometimeline(App $a, $uid, $mode = 1)
1197 $conversations = [];
1199 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1200 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1201 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1202 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1203 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1204 $create_user = PConfig::get($uid, 'statusnet', 'create_user');
1206 // "create_user" is deactivated, since currently you cannot add users manually by now
1207 $create_user = true;
1209 logger("statusnet_fetchhometimeline: Fetching for user " . $uid, LOGGER_DEBUG);
1211 require_once 'include/items.php';
1213 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1215 $own_contact = statusnet_fetch_own_contact($a, $uid);
1217 $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1218 intval($own_contact),
1221 if (DBA::isResult($r)) {
1222 $nick = $r[0]["nick"];
1224 logger("statusnet_fetchhometimeline: Own GNU Social contact not found for user " . $uid, LOGGER_DEBUG);
1228 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1231 if (DBA::isResult($r)) {
1234 logger("statusnet_fetchhometimeline: Own contact not found for user " . $uid, LOGGER_DEBUG);
1238 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1240 if (!DBA::isResult($u)) {
1241 logger("statusnet_fetchhometimeline: Own user not found for user " . $uid, LOGGER_DEBUG);
1245 $parameters = ["exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true];
1246 //$parameters["count"] = 200;
1249 // Fetching timeline
1250 $lastid = PConfig::get($uid, 'statusnet', 'lasthometimelineid');
1253 $first_time = ($lastid == "");
1255 if ($lastid != "") {
1256 $parameters["since_id"] = $lastid;
1259 $items = $connection->get('statuses/home_timeline', $parameters);
1261 if (!is_array($items)) {
1262 if (is_object($items) && isset($items->error)) {
1263 $errormsg = $items->error;
1264 } elseif (is_object($items)) {
1265 $errormsg = print_r($items, true);
1266 } elseif (is_string($items) || is_float($items) || is_int($items)) {
1269 $errormsg = "Unknown error";
1272 logger("statusnet_fetchhometimeline: Error fetching home timeline: " . $errormsg, LOGGER_DEBUG);
1276 $posts = array_reverse($items);
1278 logger("statusnet_fetchhometimeline: Fetching timeline for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1280 if (count($posts)) {
1281 foreach ($posts as $post) {
1283 if ($post->id > $lastid) {
1284 $lastid = $post->id;
1291 if (isset($post->statusnet_conversation_id)) {
1292 if (!isset($conversations[$post->statusnet_conversation_id])) {
1293 statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1294 $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1297 $postarray = statusnet_createpost($a, $uid, $post, $self, $create_user, true);
1299 if (trim($postarray['body']) == "") {
1303 $item = Item::insert($postarray);
1304 $postarray["id"] = $item;
1306 logger('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1310 PConfig::set($uid, 'statusnet', 'lasthometimelineid', $lastid);
1313 // Fetching mentions
1314 $lastid = PConfig::get($uid, 'statusnet', 'lastmentionid');
1315 $first_time = ($lastid == "");
1317 if ($lastid != "") {
1318 $parameters["since_id"] = $lastid;
1321 $items = $connection->get('statuses/mentions_timeline', $parameters);
1323 if (!is_array($items)) {
1324 logger("statusnet_fetchhometimeline: Error fetching mentions: " . print_r($items, true), LOGGER_DEBUG);
1328 $posts = array_reverse($items);
1330 logger("statusnet_fetchhometimeline: Fetching mentions for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1332 if (count($posts)) {
1333 foreach ($posts as $post) {
1334 if ($post->id > $lastid) {
1335 $lastid = $post->id;
1342 $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1344 if (isset($post->statusnet_conversation_id)) {
1345 if (!isset($conversations[$post->statusnet_conversation_id])) {
1346 statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1347 $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1350 if (trim($postarray['body']) == "") {
1354 $item = Item::insert($postarray);
1356 logger('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted mention timeline item ' . $item);
1361 PConfig::set($uid, 'statusnet', 'lastmentionid', $lastid);
1364 function statusnet_complete_conversation(App $a, $uid, $self, $create_user, $nick, $conversation)
1366 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1367 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1368 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1369 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1370 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1371 $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1373 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1375 $parameters["count"] = 200;
1377 $items = $connection->get('statusnet/conversation/' . $conversation, $parameters);
1378 if (is_array($items)) {
1379 $posts = array_reverse($items);
1381 foreach ($posts as $post) {
1382 $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1384 if (trim($postarray['body']) == "") {
1388 $item = Item::insert($postarray);
1389 $postarray["id"] = $item;
1391 logger('statusnet_complete_conversation: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1396 function statusnet_convertmsg(App $a, $body, $no_tags = false)
1398 require_once "include/items.php";
1400 $body = preg_replace("=\[url\=https?://([0-9]*).([0-9]*).([0-9]*).([0-9]*)/([0-9]*)\](.*?)\[\/url\]=ism", "$1.$2.$3.$4/$5", $body);
1402 $URLSearchString = "^\[\]";
1403 $links = preg_match_all("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1411 foreach ($matches AS $match) {
1412 $search = "[url=" . $match[1] . "]" . $match[2] . "[/url]";
1414 logger("statusnet_convertmsg: expanding url " . $match[1], LOGGER_DEBUG);
1416 $expanded_url = Network::finalUrl($match[1]);
1418 logger("statusnet_convertmsg: fetching data for " . $expanded_url, LOGGER_DEBUG);
1420 $oembed_data = OEmbed::fetchURL($expanded_url, true);
1422 logger("statusnet_convertmsg: fetching data: done", LOGGER_DEBUG);
1425 $type = $oembed_data->type;
1428 if ($oembed_data->type == "video") {
1429 //$body = str_replace($search, "[video]".$expanded_url."[/video]", $body);
1430 $type = $oembed_data->type;
1431 $footerurl = $expanded_url;
1432 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1434 $body = str_replace($search, $footerlink, $body);
1435 } elseif (($oembed_data->type == "photo") && isset($oembed_data->url)) {
1436 $body = str_replace($search, "[url=" . $expanded_url . "][img]" . $oembed_data->url . "[/img][/url]", $body);
1437 } elseif ($oembed_data->type != "link") {
1438 $body = str_replace($search, "[url=" . $expanded_url . "]" . $expanded_url . "[/url]", $body);
1440 $img_str = Network::fetchUrl($expanded_url, true, $redirects, 4);
1442 $tempfile = tempnam(get_temppath(), "cache");
1443 file_put_contents($tempfile, $img_str);
1444 $mime = mime_content_type($tempfile);
1447 if (substr($mime, 0, 6) == "image/") {
1449 $body = str_replace($search, "[img]" . $expanded_url . "[/img]", $body);
1451 $type = $oembed_data->type;
1452 $footerurl = $expanded_url;
1453 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1455 $body = str_replace($search, $footerlink, $body);
1460 if ($footerurl != "") {
1461 $footer = add_page_info($footerurl);
1464 if (($footerlink != "") && (trim($footer) != "")) {
1465 $removedlink = trim(str_replace($footerlink, "", $body));
1467 if (($removedlink == "") || strstr($body, $removedlink)) {
1468 $body = $removedlink;
1476 return ["body" => $body, "tags" => ""];
1481 $cnt = preg_match_all("/([!#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1483 foreach ($matches as $mtch) {
1484 if (strlen($str_tags)) {
1488 if ($mtch[1] == "#") {
1489 // Replacing the hash tags that are directed to the GNU Social server with internal links
1490 $snhash = "#[url=" . $mtch[2] . "]" . $mtch[3] . "[/url]";
1491 $frdchash = '#[url=' . $a->getBaseURL() . '/search?tag=' . rawurlencode($mtch[3]) . ']' . $mtch[3] . '[/url]';
1492 $body = str_replace($snhash, $frdchash, $body);
1494 $str_tags .= $frdchash;
1496 $str_tags .= "@[url=" . $mtch[2] . "]" . $mtch[3] . "[/url]";
1499 // There is a problem with links with to GNU Social groups, so these links are stored with "@" like friendica groups
1500 //$str_tags .= $mtch[1]."[url=".$mtch[2]."]".$mtch[3]."[/url]";
1504 return ["body" => $body, "tags" => $str_tags];
1507 function statusnet_fetch_own_contact(App $a, $uid)
1509 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1510 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1511 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1512 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1513 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1514 $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1518 if ($own_url == "") {
1519 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1521 // Fetching user data
1522 $user = $connection->get('account/verify_credentials');
1524 PConfig::set($uid, 'statusnet', 'own_url', normalise_link($user->statusnet_profile_url));
1526 $contact_id = statusnet_fetch_contact($uid, $user, true);
1528 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1529 intval($uid), DBA::escape($own_url));
1530 if (DBA::isResult($r)) {
1531 $contact_id = $r[0]["id"];
1533 PConfig::delete($uid, 'statusnet', 'own_url');
1539 function statusnet_is_retweet(App $a, $uid, $body)
1541 $body = trim($body);
1543 // Skip if it isn't a pure repeated messages
1544 // Does it start with a share?
1545 if (strpos($body, "[share") > 0) {
1549 // Does it end with a share?
1550 if (strlen($body) > (strrpos($body, "[/share]") + 8)) {
1554 $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
1555 // Skip if there is no shared message in there
1556 if ($body == $attributes) {
1561 preg_match("/link='(.*?)'/ism", $attributes, $matches);
1562 if (!empty($matches[1])) {
1563 $link = $matches[1];
1566 preg_match('/link="(.*?)"/ism', $attributes, $matches);
1567 if (!empty($matches[1])) {
1568 $link = $matches[1];
1571 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1572 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1573 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1574 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1575 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1576 $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1578 $id = preg_replace("=https?://" . $hostname . "/notice/(.*)=ism", "$1", $link);
1584 logger('statusnet_is_retweet: Retweeting id ' . $id . ' for user ' . $uid, LOGGER_DEBUG);
1586 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1588 $result = $connection->post('statuses/retweet/' . $id);
1590 logger('statusnet_is_retweet: result ' . print_r($result, true), LOGGER_DEBUG);
1592 return isset($result->id);