]> git.mxchange.org Git - friendica-addons.git/blob - statusnet/statusnet.php
Restructure Cache to follow new paradigm
[friendica-addons.git] / statusnet / statusnet.php
1 <?php
2
3 /**
4  * Name: GNU Social Connector
5  * Description: Bidirectional (posting, relaying and reading) connector for GNU Social.
6  * Version: 1.0.5
7  * Author: Tobias Diekershoff <https://f.diekershoff.de/profile/tobias>
8  * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
9  *
10  * Copyright (c) 2011-2013 Tobias Diekershoff, Michael Vogel
11  * All rights reserved.
12  *
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.
23  *
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.
34  *
35  */
36 define('STATUSNET_DEFAULT_POLL_INTERVAL', 5); // given in minutes
37
38 require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'statusnetoauth.php';
39
40 use CodebirdSN\CodebirdSN;
41 use Friendica\App;
42 use Friendica\Content\OEmbed;
43 use Friendica\Content\PageInfo;
44 use Friendica\Content\Text\HTML;
45 use Friendica\Content\Text\Plaintext;
46 use Friendica\Core\Hook;
47 use Friendica\Core\Logger;
48 use Friendica\Core\Protocol;
49 use Friendica\Core\Renderer;
50 use Friendica\Database\DBA;
51 use Friendica\DI;
52 use Friendica\Model\Contact;
53 use Friendica\Model\Group;
54 use Friendica\Model\Item;
55 use Friendica\Model\Photo;
56 use Friendica\Model\Post;
57 use Friendica\Model\User;
58 use Friendica\Protocol\Activity;
59 use Friendica\Util\DateTimeFormat;
60 use Friendica\Util\Strings;
61 use GuzzleHttp\Exception\TransferException;
62
63 function statusnet_install()
64 {
65         //  we need some hooks, for the configuration and for sending tweets
66         Hook::register('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
67         Hook::register('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
68         Hook::register('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
69         Hook::register('hook_fork', 'addon/statusnet/statusnet.php', 'statusnet_hook_fork');
70         Hook::register('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
71         Hook::register('jot_networks', 'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
72         Hook::register('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
73         Hook::register('prepare_body', 'addon/statusnet/statusnet.php', 'statusnet_prepare_body');
74         Hook::register('check_item_notification', 'addon/statusnet/statusnet.php', 'statusnet_check_item_notification');
75         Logger::notice("installed GNU Social");
76 }
77
78 function statusnet_check_item_notification(App $a, &$notification_data)
79 {
80         if (DI::pConfig()->get($notification_data["uid"], 'statusnet', 'post')) {
81                 $notification_data["profiles"][] = DI::pConfig()->get($notification_data["uid"], 'statusnet', 'own_url');
82         }
83 }
84
85 function statusnet_jot_nets(App $a, array &$jotnets_fields)
86 {
87         if (!local_user()) {
88                 return;
89         }
90
91         if (DI::pConfig()->get(local_user(), 'statusnet', 'post')) {
92                 $jotnets_fields[] = [
93                         'type' => 'checkbox',
94                         'field' => [
95                                 'statusnet_enable',
96                                 DI::l10n()->t('Post to GNU Social'),
97                                 DI::pConfig()->get(local_user(), 'statusnet', 'post_by_default')
98                         ]
99                 ];
100         }
101 }
102
103 function statusnet_settings_post(App $a, $post)
104 {
105         if (!local_user()) {
106                 return;
107         }
108         // don't check GNU Social settings if GNU Social submit button is not clicked
109         if (empty($_POST['statusnet-submit'])) {
110                 return;
111         }
112
113         if (isset($_POST['statusnet-disconnect'])) {
114                 /*               * *
115                  * if the GNU Social-disconnect checkbox is set, clear the GNU Social configuration
116                  */
117                 DI::pConfig()->delete(local_user(), 'statusnet', 'consumerkey');
118                 DI::pConfig()->delete(local_user(), 'statusnet', 'consumersecret');
119                 DI::pConfig()->delete(local_user(), 'statusnet', 'post');
120                 DI::pConfig()->delete(local_user(), 'statusnet', 'post_by_default');
121                 DI::pConfig()->delete(local_user(), 'statusnet', 'oauthtoken');
122                 DI::pConfig()->delete(local_user(), 'statusnet', 'oauthsecret');
123                 DI::pConfig()->delete(local_user(), 'statusnet', 'baseapi');
124                 DI::pConfig()->delete(local_user(), 'statusnet', 'lastid');
125                 DI::pConfig()->delete(local_user(), 'statusnet', 'mirror_posts');
126                 DI::pConfig()->delete(local_user(), 'statusnet', 'import');
127                 DI::pConfig()->delete(local_user(), 'statusnet', 'create_user');
128                 DI::pConfig()->delete(local_user(), 'statusnet', 'own_url');
129         } else {
130                 if (isset($_POST['statusnet-preconf-apiurl'])) {
131                         /*                       * *
132                          * If the user used one of the preconfigured GNU Social server credentials
133                          * use them. All the data are available in the global config.
134                          * Check the API Url never the less and blame the admin if it's not working ^^
135                          */
136                         $globalsn = DI::config()->get('statusnet', 'sites');
137                         foreach ($globalsn as $asn) {
138                                 if ($asn['apiurl'] == $_POST['statusnet-preconf-apiurl']) {
139                                         $apibase = $asn['apiurl'];
140                                         $c = DI::httpClient()->fetch($apibase . 'statusnet/version.xml');
141                                         if (strlen($c) > 0) {
142                                                 DI::pConfig()->set(local_user(), 'statusnet', 'consumerkey', $asn['consumerkey']);
143                                                 DI::pConfig()->set(local_user(), 'statusnet', 'consumersecret', $asn['consumersecret']);
144                                                 DI::pConfig()->set(local_user(), 'statusnet', 'baseapi', $asn['apiurl']);
145                                                 //DI::pConfig()->set(local_user(), 'statusnet', 'application_name', $asn['applicationname'] );
146                                         } else {
147                                                 notice(DI::l10n()->t('Please contact your site administrator.<br />The provided API URL is not valid.') . EOL . $asn['apiurl'] . EOL);
148                                         }
149                                 }
150                         }
151                         DI::baseUrl()->redirect('settings/connectors');
152                 } else {
153                         if (isset($_POST['statusnet-consumersecret'])) {
154                                 //  check if we can reach the API of the GNU Social server
155                                 //  we'll check the API Version for that, if we don't get one we'll try to fix the path but will
156                                 //  resign quickly after this one try to fix the path ;-)
157                                 $apibase = $_POST['statusnet-baseapi'];
158                                 $c = DI::httpClient()->fetch($apibase . 'statusnet/version.xml');
159                                 if (strlen($c) > 0) {
160                                         //  ok the API path is correct, let's save the settings
161                                         DI::pConfig()->set(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
162                                         DI::pConfig()->set(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
163                                         DI::pConfig()->set(local_user(), 'statusnet', 'baseapi', $apibase);
164                                         //DI::pConfig()->set(local_user(), 'statusnet', 'application_name', $_POST['statusnet-applicationname'] );
165                                 } else {
166                                         //  the API path is not correct, maybe missing trailing / ?
167                                         $apibase = $apibase . '/';
168                                         $c = DI::httpClient()->fetch($apibase . 'statusnet/version.xml');
169                                         if (strlen($c) > 0) {
170                                                 //  ok the API path is now correct, let's save the settings
171                                                 DI::pConfig()->set(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
172                                                 DI::pConfig()->set(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
173                                                 DI::pConfig()->set(local_user(), 'statusnet', 'baseapi', $apibase);
174                                         } else {
175                                                 //  still not the correct API base, let's do noting
176                                                 notice(DI::l10n()->t('We could not contact the GNU Social API with the Path you entered.') . EOL);
177                                         }
178                                 }
179                                 DI::baseUrl()->redirect('settings/connectors');
180                         } else {
181                                 if (isset($_POST['statusnet-pin'])) {
182                                         //  if the user supplied us with a PIN from GNU Social, let the magic of OAuth happen
183                                         $api = DI::pConfig()->get(local_user(), 'statusnet', 'baseapi');
184                                         $ckey = DI::pConfig()->get(local_user(), 'statusnet', 'consumerkey');
185                                         $csecret = DI::pConfig()->get(local_user(), 'statusnet', 'consumersecret');
186                                         //  the token and secret for which the PIN was generated were hidden in the settings
187                                         //  form as token and token2, we need a new connection to GNU Social using these token
188                                         //  and secret to request a Access Token with the PIN
189                                         $connection = new StatusNetOAuth($api, $ckey, $csecret, $_POST['statusnet-token'], $_POST['statusnet-token2']);
190                                         $token = $connection->getAccessToken($_POST['statusnet-pin']);
191                                         //  ok, now that we have the Access Token, save them in the user config
192                                         DI::pConfig()->set(local_user(), 'statusnet', 'oauthtoken', $token['oauth_token']);
193                                         DI::pConfig()->set(local_user(), 'statusnet', 'oauthsecret', $token['oauth_token_secret']);
194                                         DI::pConfig()->set(local_user(), 'statusnet', 'post', 1);
195                                         DI::pConfig()->set(local_user(), 'statusnet', 'post_taglinks', 1);
196                                         //  reload the Addon Settings page, if we don't do it see Bug #42
197                                         DI::baseUrl()->redirect('settings/connectors');
198                                 } else {
199                                         //  if no PIN is supplied in the POST variables, the user has changed the setting
200                                         //  to post a dent for every new __public__ posting to the wall
201                                         DI::pConfig()->set(local_user(), 'statusnet', 'post', intval($_POST['statusnet-enable']));
202                                         DI::pConfig()->set(local_user(), 'statusnet', 'post_by_default', intval($_POST['statusnet-default']));
203                                         DI::pConfig()->set(local_user(), 'statusnet', 'mirror_posts', intval($_POST['statusnet-mirror']));
204                                         DI::pConfig()->set(local_user(), 'statusnet', 'import', intval($_POST['statusnet-import']));
205                                         DI::pConfig()->set(local_user(), 'statusnet', 'create_user', intval($_POST['statusnet-create_user']));
206
207                                         if (!intval($_POST['statusnet-mirror']))
208                                                 DI::pConfig()->delete(local_user(), 'statusnet', 'lastid');
209                                 }
210                         }
211                 }
212         }
213 }
214
215 function statusnet_settings(App $a, &$s)
216 {
217         if (!local_user()) {
218                 return;
219         }
220
221         $user = User::getById(local_user());
222
223         DI::page()['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . DI::baseUrl()->get() . '/addon/statusnet/statusnet.css' . '" media="all" />' . "\r\n";
224         /*       * *
225          * 1) Check that we have a base api url and a consumer key & secret
226          * 2) If no OAuthtoken & stuff is present, generate button to get some
227          *    allow the user to cancel the connection process at this step
228          * 3) Checkbox for "Send public notices (respect size limitation)
229          */
230         $api     = DI::pConfig()->get(local_user(), 'statusnet', 'baseapi');
231         $ckey    = DI::pConfig()->get(local_user(), 'statusnet', 'consumerkey');
232         $csecret = DI::pConfig()->get(local_user(), 'statusnet', 'consumersecret');
233         $otoken  = DI::pConfig()->get(local_user(), 'statusnet', 'oauthtoken');
234         $osecret = DI::pConfig()->get(local_user(), 'statusnet', 'oauthsecret');
235         $enabled = DI::pConfig()->get(local_user(), 'statusnet', 'post');
236         $checked = (($enabled) ? ' checked="checked" ' : '');
237         $defenabled = DI::pConfig()->get(local_user(), 'statusnet', 'post_by_default');
238         $defchecked = (($defenabled) ? ' checked="checked" ' : '');
239         $mirrorenabled = DI::pConfig()->get(local_user(), 'statusnet', 'mirror_posts');
240         $mirrorchecked = (($mirrorenabled) ? ' checked="checked" ' : '');
241         $import = DI::pConfig()->get(local_user(), 'statusnet', 'import');
242         $importselected = ["", "", ""];
243         $importselected[$import] = ' selected="selected"';
244         //$importenabled = DI::pConfig()->get(local_user(),'statusnet','import');
245         //$importchecked = (($importenabled) ? ' checked="checked" ' : '');
246         $create_userenabled = DI::pConfig()->get(local_user(), 'statusnet', 'create_user');
247         $create_userchecked = (($create_userenabled) ? ' checked="checked" ' : '');
248
249         $css = (($enabled) ? '' : '-disabled');
250
251         $s .= '<span id="settings_statusnet_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_statusnet_expanded\'); openClose(\'settings_statusnet_inflated\');">';
252         $s .= '<img class="connector' . $css . '" src="images/gnusocial.png" /><h3 class="connector">' . DI::l10n()->t('GNU Social Import/Export/Mirror') . '</h3>';
253         $s .= '</span>';
254         $s .= '<div id="settings_statusnet_expanded" class="settings-block" style="display: none;">';
255         $s .= '<span class="fakelink" onclick="openClose(\'settings_statusnet_expanded\'); openClose(\'settings_statusnet_inflated\');">';
256         $s .= '<img class="connector' . $css . '" src="images/gnusocial.png" /><h3 class="connector">' . DI::l10n()->t('GNU Social Import/Export/Mirror') . '</h3>';
257         $s .= '</span>';
258
259         if ((!$ckey) && (!$csecret)) {
260                 /*               * *
261                  * no consumer keys
262                  */
263                 $globalsn = DI::config()->get('statusnet', 'sites');
264                 /*               * *
265                  * lets check if we have one or more globally configured GNU Social
266                  * server OAuth credentials in the configuration. If so offer them
267                  * with a little explanation to the user as choice - otherwise
268                  * ignore this option entirely.
269                  */
270                 if (!$globalsn == null) {
271                         $s .= '<h4>' . DI::l10n()->t('Globally Available GNU Social OAuthKeys') . '</h4>';
272                         $s .= '<p>' . DI::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>';
273                         $s .= '<div id="statusnet-preconf-wrapper">';
274                         foreach ($globalsn as $asn) {
275                                 $s .= '<input type="radio" name="statusnet-preconf-apiurl" value="' . $asn['apiurl'] . '">' . $asn['sitename'] . '<br />';
276                         }
277                         $s .= '<p></p><div class="clear"></div></div>';
278                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . DI::l10n()->t('Save Settings') . '" /></div>';
279                 }
280                 $s .= '<h4>' . DI::l10n()->t('Provide your own OAuth Credentials') . '</h4>';
281                 $s .= '<p>' . DI::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>';
282                 $s .= '<div id="statusnet-consumer-wrapper">';
283                 $s .= '<label id="statusnet-consumerkey-label" for="statusnet-consumerkey">' . DI::l10n()->t('OAuth Consumer Key') . '</label>';
284                 $s .= '<input id="statusnet-consumerkey" type="text" name="statusnet-consumerkey" size="35" /><br />';
285                 $s .= '<div class="clear"></div>';
286                 $s .= '<label id="statusnet-consumersecret-label" for="statusnet-consumersecret">' . DI::l10n()->t('OAuth Consumer Secret') . '</label>';
287                 $s .= '<input id="statusnet-consumersecret" type="text" name="statusnet-consumersecret" size="35" /><br />';
288                 $s .= '<div class="clear"></div>';
289                 $s .= '<label id="statusnet-baseapi-label" for="statusnet-baseapi">' . DI::l10n()->t("Base API Path \x28remember the trailing /\x29") . '</label>';
290                 $s .= '<input id="statusnet-baseapi" type="text" name="statusnet-baseapi" size="35" /><br />';
291                 $s .= '<div class="clear"></div>';
292                 //$s .= '<label id="statusnet-applicationname-label" for="statusnet-applicationname">'.DI::l10n()->t('GNU Socialapplication name').'</label>';
293                 //$s .= '<input id="statusnet-applicationname" type="text" name="statusnet-applicationname" size="35" /><br />';
294                 $s .= '<p></p><div class="clear"></div>';
295                 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . DI::l10n()->t('Save Settings') . '" /></div>';
296                 $s .= '</div>';
297         } else {
298                 /*               * *
299                  * ok we have a consumer key pair now look into the OAuth stuff
300                  */
301                 if ((!$otoken) && (!$osecret)) {
302                         /*                       * *
303                          * the user has not yet connected the account to GNU Social
304                          * get a temporary OAuth key/secret pair and display a button with
305                          * which the user can request a PIN to connect the account to a
306                          * account at GNU Social
307                          */
308                         $connection = new StatusNetOAuth($api, $ckey, $csecret);
309                         $request_token = $connection->getRequestToken('oob');
310                         $token = $request_token['oauth_token'];
311                         /*                       * *
312                          *  make some nice form
313                          */
314                         $s .= '<p>' . DI::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>';
315                         $s .= '<a href="' . $connection->getAuthorizeURL($token, False) . '" target="_statusnet"><img src="addon/statusnet/signinwithstatusnet.png" alt="' . DI::l10n()->t('Log in with GNU Social') . '"></a>';
316                         $s .= '<div id="statusnet-pin-wrapper">';
317                         $s .= '<label id="statusnet-pin-label" for="statusnet-pin">' . DI::l10n()->t('Copy the security code from GNU Social here') . '</label>';
318                         $s .= '<input id="statusnet-pin" type="text" name="statusnet-pin" />';
319                         $s .= '<input id="statusnet-token" type="hidden" name="statusnet-token" value="' . $token . '" />';
320                         $s .= '<input id="statusnet-token2" type="hidden" name="statusnet-token2" value="' . $request_token['oauth_token_secret'] . '" />';
321                         $s .= '</div><div class="clear"></div>';
322                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . DI::l10n()->t('Save Settings') . '" /></div>';
323                         $s .= '<h4>' . DI::l10n()->t('Cancel Connection Process') . '</h4>';
324                         $s .= '<div id="statusnet-cancel-wrapper">';
325                         $s .= '<p>' . DI::l10n()->t('Current GNU Social API is') . ': ' . $api . '</p>';
326                         $s .= '<label id="statusnet-cancel-label" for="statusnet-cancel">' . DI::l10n()->t('Cancel GNU Social Connection') . '</label>';
327                         $s .= '<input id="statusnet-cancel" type="checkbox" name="statusnet-disconnect" value="1" />';
328                         $s .= '</div><div class="clear"></div>';
329                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . DI::l10n()->t('Save Settings') . '" /></div>';
330                 } else {
331                         /*                       * *
332                          *  we have an OAuth key / secret pair for the user
333                          *  so let's give a chance to disable the postings to GNU Social
334                          */
335                         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
336                         $details = $connection->get('account/verify_credentials');
337                         if (!empty($details)) {
338                                 $s .= '<div id="statusnet-info" ><img id="statusnet-avatar" src="' . $details->profile_image_url . '" /><p id="statusnet-info-block">' . DI::l10n()->t('Currently connected to: ') . '<a href="' . $details->statusnet_profile_url . '" target="_statusnet">' . $details->screen_name . '</a><br /><em>' . $details->description . '</em></p></div>';
339                         }
340                         $s .= '<p>' . DI::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>';
341                         if ($user['hidewall']) {
342                                 $s .= '<p>' . DI::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>';
343                         }
344                         $s .= '<div id="statusnet-enable-wrapper">';
345                         $s .= '<label id="statusnet-enable-label" for="statusnet-checkbox">' . DI::l10n()->t('Allow posting to GNU Social') . '</label>';
346                         $s .= '<input id="statusnet-checkbox" type="checkbox" name="statusnet-enable" value="1" ' . $checked . '/>';
347                         $s .= '<div class="clear"></div>';
348                         $s .= '<label id="statusnet-default-label" for="statusnet-default">' . DI::l10n()->t('Send public postings to GNU Social by default') . '</label>';
349                         $s .= '<input id="statusnet-default" type="checkbox" name="statusnet-default" value="1" ' . $defchecked . '/>';
350                         $s .= '<div class="clear"></div>';
351
352                         $s .= '<label id="statusnet-mirror-label" for="statusnet-mirror">' . DI::l10n()->t('Mirror all posts from GNU Social that are no replies or repeated messages') . '</label>';
353                         $s .= '<input id="statusnet-mirror" type="checkbox" name="statusnet-mirror" value="1" ' . $mirrorchecked . '/>';
354
355                         $s .= '<div class="clear"></div>';
356                         $s .= '</div>';
357
358                         $s .= '<label id="statusnet-import-label" for="statusnet-import">' . DI::l10n()->t('Import the remote timeline') . '</label>';
359                         //$s .= '<input id="statusnet-import" type="checkbox" name="statusnet-import" value="1" '. $importchecked . '/>';
360
361                         $s .= '<select name="statusnet-import" id="statusnet-import" />';
362                         $s .= '<option value="0" ' . $importselected[0] . '>' . DI::l10n()->t("Disabled") . '</option>';
363                         $s .= '<option value="1" ' . $importselected[1] . '>' . DI::l10n()->t("Full Timeline") . '</option>';
364                         $s .= '<option value="2" ' . $importselected[2] . '>' . DI::l10n()->t("Only Mentions") . '</option>';
365                         $s .= '</select>';
366                         $s .= '<div class="clear"></div>';
367                         /*
368                           $s .= '<label id="statusnet-create_user-label" for="statusnet-create_user">'.DI::l10n()->t('Automatically create contacts').'</label>';
369                           $s .= '<input id="statusnet-create_user" type="checkbox" name="statusnet-create_user" value="1" '. $create_userchecked . '/>';
370                           $s .= '<div class="clear"></div>';
371                          */
372                         $s .= '<div id="statusnet-disconnect-wrapper">';
373                         $s .= '<label id="statusnet-disconnect-label" for="statusnet-disconnect">' . DI::l10n()->t('Clear OAuth configuration') . '</label>';
374                         $s .= '<input id="statusnet-disconnect" type="checkbox" name="statusnet-disconnect" value="1" />';
375                         $s .= '</div><div class="clear"></div>';
376                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . DI::l10n()->t('Save Settings') . '" /></div>';
377                 }
378         }
379         $s .= '</div><div class="clear"></div>';
380 }
381
382 function statusnet_hook_fork(App $a, array &$b)
383 {
384         if ($b['name'] != 'notifier_normal') {
385                 return;
386         }
387
388         $post = $b['data'];
389
390         // Deleting and editing is not supported by the addon
391         if ($post['deleted'] || ($post['created'] !== $post['edited'])) {
392                 $b['execute'] = false;
393                 return;
394         }
395
396         // if post comes from GNU Social don't send it back
397         if ($post['extid'] == Protocol::STATUSNET) {
398                 $b['execute'] = false;
399                 return;
400         }
401
402         if ($post['app'] == 'StatusNet') {
403                 $b['execute'] = false;
404                 return;
405         }
406
407         if (DI::pConfig()->get($post['uid'], 'statusnet', 'import')) {
408                 // Don't fork if it isn't a reply to a GNU Social post
409                 if (($post['parent'] != $post['id']) && !Post::exists(['id' => $post['parent'], 'network' => Protocol::STATUSNET])) {
410                         Logger::notice('No GNU Social parent found for item ' . $post['id']);
411                         $b['execute'] = false;
412                         return;
413                 }
414         } else {
415                 // Comments are never exported when we don't import the GNU Social timeline
416                 if (!strstr($post['postopts'], 'statusnet') || ($post['parent'] != $post['id']) || $post['private']) {
417                         $b['execute'] = false;
418                         return;
419                 }
420         }
421 }
422
423 function statusnet_post_local(App $a, &$b)
424 {
425         if ($b['edit']) {
426                 return;
427         }
428
429         if (!local_user() || (local_user() != $b['uid'])) {
430                 return;
431         }
432
433         $statusnet_post = DI::pConfig()->get(local_user(), 'statusnet', 'post');
434         $statusnet_enable = (($statusnet_post && !empty($_REQUEST['statusnet_enable'])) ? intval($_REQUEST['statusnet_enable']) : 0);
435
436         // if API is used, default to the chosen settings
437         if ($b['api_source'] && intval(DI::pConfig()->get(local_user(), 'statusnet', 'post_by_default'))) {
438                 $statusnet_enable = 1;
439         }
440
441         if (!$statusnet_enable) {
442                 return;
443         }
444
445         if (strlen($b['postopts'])) {
446                 $b['postopts'] .= ',';
447         }
448
449         $b['postopts'] .= 'statusnet';
450 }
451
452 function statusnet_action(App $a, $uid, $pid, $action)
453 {
454         $api = DI::pConfig()->get($uid, 'statusnet', 'baseapi');
455         $ckey = DI::pConfig()->get($uid, 'statusnet', 'consumerkey');
456         $csecret = DI::pConfig()->get($uid, 'statusnet', 'consumersecret');
457         $otoken = DI::pConfig()->get($uid, 'statusnet', 'oauthtoken');
458         $osecret = DI::pConfig()->get($uid, 'statusnet', 'oauthsecret');
459
460         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
461
462         Logger::debug("statusnet_action '" . $action . "' ID: " . $pid);
463
464         switch ($action) {
465                 case "delete":
466                         $result = $connection->post("statuses/destroy/" . $pid);
467                         break;
468                 case "like":
469                         $result = $connection->post("favorites/create/" . $pid);
470                         break;
471                 case "unlike":
472                         $result = $connection->post("favorites/destroy/" . $pid);
473                         break;
474         }
475         Logger::info("statusnet_action '" . $action . "' send, result: " . print_r($result, true));
476 }
477
478 function statusnet_post_hook(App $a, &$b)
479 {
480         /**
481          * Post to GNU Social
482          */
483         if (!DI::pConfig()->get($b["uid"], 'statusnet', 'import')) {
484                 if ($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))
485                         return;
486         }
487
488         $b['body'] = Post\Media::addAttachmentsToBody($b['uri-id'], $b['body']);
489
490         $api = DI::pConfig()->get($b["uid"], 'statusnet', 'baseapi');
491         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
492
493         if ($b['parent'] != $b['id']) {
494                 Logger::debug("statusnet_post_hook: parameter " . print_r($b, true));
495
496                 // Looking if its a reply to a GNU Social post
497                 $hostlength = strlen($hostname) + 2;
498                 if ((substr($b["parent-uri"], 0, $hostlength) != $hostname . "::") && (substr($b["extid"], 0, $hostlength) != $hostname . "::") && (substr($b["thr-parent"], 0, $hostlength) != $hostname . "::")) {
499                         Logger::notice("statusnet_post_hook: no GNU Social post " . $b["parent"]);
500                         return;
501                 }
502
503                 $condition = ['uri' => $b["thr-parent"], 'uid' => $b["uid"]];
504                 $orig_post = Post::selectFirst(['author-link', 'uri'], $condition);
505                 if (!DBA::isResult($orig_post)) {
506                         Logger::notice("statusnet_post_hook: no parent found " . $b["thr-parent"]);
507                         return;
508                 } else {
509                         $iscomment = true;
510                 }
511
512                 $nick = preg_replace("=https?://(.*)/(.*)=ism", "$2", $orig_post["author-link"]);
513
514                 $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nick . "[/url]";
515                 $nicknameplain = "@" . $nick;
516
517                 Logger::info("statusnet_post_hook: comparing " . $nickname . " and " . $nicknameplain . " with " . $b["body"]);
518                 if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
519                         $b["body"] = $nickname . " " . $b["body"];
520                 }
521
522                 Logger::info("statusnet_post_hook: parent found " . print_r($orig_post, true));
523         } else {
524                 $iscomment = false;
525
526                 if ($b['private'] || !strstr($b['postopts'], 'statusnet')) {
527                         return;
528                 }
529
530                 // Dont't post if the post doesn't belong to us.
531                 // This is a check for forum postings
532                 $self = DBA::selectFirst('contact', ['id'], ['uid' => $b['uid'], 'self' => true]);
533                 if ($b['contact-id'] != $self['id']) {
534                         return;
535                 }
536         }
537
538         if (($b['verb'] == Activity::POST) && $b['deleted']) {
539                 statusnet_action($a, $b["uid"], substr($orig_post["uri"], $hostlength), "delete");
540         }
541
542         if ($b['verb'] == Activity::LIKE) {
543                 Logger::info("statusnet_post_hook: parameter 2 " . substr($b["thr-parent"], $hostlength));
544                 if ($b['deleted'])
545                         statusnet_action($a, $b["uid"], substr($b["thr-parent"], $hostlength), "unlike");
546                 else
547                         statusnet_action($a, $b["uid"], substr($b["thr-parent"], $hostlength), "like");
548                 return;
549         }
550
551         if ($b['deleted'] || ($b['created'] !== $b['edited'])) {
552                 return;
553         }
554
555         // if posts comes from GNU Social don't send it back
556         if ($b['extid'] == Protocol::STATUSNET) {
557                 return;
558         }
559
560         if ($b['app'] == "StatusNet") {
561                 return;
562         }
563
564         Logger::notice('GNU Socialpost invoked');
565
566         DI::pConfig()->load($b['uid'], 'statusnet');
567
568         $api     = DI::pConfig()->get($b['uid'], 'statusnet', 'baseapi');
569         $ckey    = DI::pConfig()->get($b['uid'], 'statusnet', 'consumerkey');
570         $csecret = DI::pConfig()->get($b['uid'], 'statusnet', 'consumersecret');
571         $otoken  = DI::pConfig()->get($b['uid'], 'statusnet', 'oauthtoken');
572         $osecret = DI::pConfig()->get($b['uid'], 'statusnet', 'oauthsecret');
573
574         if ($ckey && $csecret && $otoken && $osecret) {
575                 // If it's a repeated message from GNU Social then do a native retweet and exit
576                 if (statusnet_is_retweet($a, $b['uid'], $b['body'])) {
577                         return;
578                 }
579
580                 $dent = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
581                 $max_char = $dent->get_maxlength(); // max. length for a dent
582
583                 DI::pConfig()->set($b['uid'], 'statusnet', 'max_char', $max_char);
584
585                 $tempfile = "";
586                 $msgarr = Plaintext::getPost($b, $max_char, true, 7);
587                 $msg = $msgarr["text"];
588
589                 if (($msg == "") && isset($msgarr["title"]))
590                         $msg = Plaintext::shorten($msgarr["title"], $max_char - 50, $b['uid']);
591
592                 $image = "";
593
594                 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
595                         $msg .= " \n" . $msgarr["url"];
596                 } elseif (isset($msgarr["image"]) && ($msgarr["type"] != "video")) {
597                         $image = $msgarr["image"];
598                 }
599
600                 if ($image != "") {
601                         $img_str = DI::httpClient()->fetch($image);
602                         $tempfile = tempnam(get_temppath(), "cache");
603                         file_put_contents($tempfile, $img_str);
604                         $postdata = ["status" => $msg, "media[]" => $tempfile];
605                 } else {
606                         $postdata = ["status" => $msg];
607                 }
608
609                 // and now send it :-)
610                 if (strlen($msg)) {
611                         if ($iscomment) {
612                                 $postdata["in_reply_to_status_id"] = substr($orig_post["uri"], $hostlength);
613                                 Logger::info('statusnet_post send reply ' . print_r($postdata, true));
614                         }
615
616                         // New code that is able to post pictures
617                         require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'codebirdsn.php';
618                         $cb = CodebirdSN::getInstance();
619                         $cb->setAPIEndpoint($api);
620                         $cb->setConsumerKey($ckey, $csecret);
621                         $cb->setToken($otoken, $osecret);
622                         $result = $cb->statuses_update($postdata);
623                         //$result = $dent->post('statuses/update', $postdata);
624                         Logger::info('statusnet_post send, result: ' . print_r($result, true) .
625                                 "\nmessage: " . $msg . "\nOriginal post: " . print_r($b, true) . "\nPost Data: " . print_r($postdata, true));
626
627                         if (!empty($result->source)) {
628                                 DI::pConfig()->set($b["uid"], "statusnet", "application_name", strip_tags($result->source));
629                         }
630
631                         if (!empty($result->error)) {
632                                 Logger::notice('Send to GNU Social failed: "' . $result->error . '"');
633                         } elseif ($iscomment) {
634                                 Logger::notice('statusnet_post: Update extid ' . $result->id . " for post id " . $b['id']);
635                                 Item::update(['extid' => $hostname . "::" . $result->id, 'body' => $result->text], ['id' => $b['id']]);
636                         }
637                 }
638                 if ($tempfile != "") {
639                         unlink($tempfile);
640                 }
641         }
642 }
643
644 function statusnet_addon_admin_post(App $a)
645 {
646         $sites = [];
647
648         foreach ($_POST['sitename'] as $id => $sitename) {
649                 $sitename = trim($sitename);
650                 $apiurl = trim($_POST['apiurl'][$id]);
651                 if (!(substr($apiurl, -1) == '/')) {
652                         $apiurl = $apiurl . '/';
653                 }
654                 $secret = trim($_POST['secret'][$id]);
655                 $key = trim($_POST['key'][$id]);
656                 //$applicationname = (!empty($_POST['applicationname']) ? Strings::escapeTags(trim($_POST['applicationname'][$id])):'');
657                 if ($sitename != "" &&
658                         $apiurl != "" &&
659                         $secret != "" &&
660                         $key != "" &&
661                         empty($_POST['delete'][$id])) {
662
663                         $sites[] = [
664                                 'sitename' => $sitename,
665                                 'apiurl' => $apiurl,
666                                 'consumersecret' => $secret,
667                                 'consumerkey' => $key,
668                                 //'applicationname' => $applicationname
669                         ];
670                 }
671         }
672
673         $sites = DI::config()->set('statusnet', 'sites', $sites);
674 }
675
676 function statusnet_addon_admin(App $a, &$o)
677 {
678         $sites = DI::config()->get('statusnet', 'sites');
679         $sitesform = [];
680         if (is_array($sites)) {
681                 foreach ($sites as $id => $s) {
682                         $sitesform[] = [
683                                 'sitename' => ["sitename[$id]", "Site name", $s['sitename'], ""],
684                                 'apiurl' => ["apiurl[$id]", "Api url", $s['apiurl'], DI::l10n()->t("Base API Path \x28remember the trailing /\x29")],
685                                 'secret' => ["secret[$id]", "Secret", $s['consumersecret'], ""],
686                                 'key' => ["key[$id]", "Key", $s['consumerkey'], ""],
687                                 //'applicationname' => Array("applicationname[$id]", "Application name", $s['applicationname'], ""),
688                                 'delete' => ["delete[$id]", "Delete", False, "Check to delete this preset"],
689                         ];
690                 }
691         }
692         /* empty form to add new site */
693         $id = count($sitesform);
694         $sitesform[] = [
695                 'sitename' => ["sitename[$id]", DI::l10n()->t("Site name"), "", ""],
696                 'apiurl' => ["apiurl[$id]", "Api url", "", DI::l10n()->t("Base API Path \x28remember the trailing /\x29")],
697                 'secret' => ["secret[$id]", DI::l10n()->t("Consumer Secret"), "", ""],
698                 'key' => ["key[$id]", DI::l10n()->t("Consumer Key"), "", ""],
699                 //'applicationname' => Array("applicationname[$id]", DI::l10n()->t("Application name"), "", ""),
700         ];
701
702         $t = Renderer::getMarkupTemplate("admin.tpl", "addon/statusnet/");
703         $o = Renderer::replaceMacros($t, [
704                 '$submit' => DI::l10n()->t('Save Settings'),
705                 '$sites' => $sitesform,
706         ]);
707 }
708
709 function statusnet_prepare_body(App $a, &$b)
710 {
711         if ($b["item"]["network"] != Protocol::STATUSNET) {
712                 return;
713         }
714
715         if ($b["preview"]) {
716                 $max_char = DI::pConfig()->get(local_user(), 'statusnet', 'max_char');
717                 if (intval($max_char) == 0) {
718                         $max_char = 140;
719                 }
720
721                 $item = $b["item"];
722                 $item["plink"] = DI::baseUrl()->get() . "/display/" . $item["guid"];
723
724                 $condition = ['uri' => $item["thr-parent"], 'uid' => local_user()];
725                 $orig_post = Post::selectFirst(['author-link', 'uri'], $condition);
726                 if (DBA::isResult($orig_post)) {
727                         $nick = preg_replace("=https?://(.*)/(.*)=ism", "$2", $orig_post["author-link"]);
728
729                         $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nick . "[/url]";
730                         $nicknameplain = "@" . $nick;
731
732                         if ((strpos($item["body"], $nickname) === false) && (strpos($item["body"], $nicknameplain) === false)) {
733                                 $item["body"] = $nickname . " " . $item["body"];
734                         }
735                 }
736
737                 $msgarr = Plaintext::getPost($item, $max_char, true, 7);
738                 $msg = $msgarr["text"];
739
740                 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
741                         $msg .= " " . $msgarr["url"];
742                 }
743
744                 if (isset($msgarr["image"])) {
745                         $msg .= " " . $msgarr["image"];
746                 }
747
748                 $b['html'] = nl2br(htmlspecialchars($msg));
749         }
750 }
751
752 function statusnet_cron(App $a, $b)
753 {
754         $last = DI::config()->get('statusnet', 'last_poll');
755
756         $poll_interval = intval(DI::config()->get('statusnet', 'poll_interval'));
757         if (!$poll_interval) {
758                 $poll_interval = STATUSNET_DEFAULT_POLL_INTERVAL;
759         }
760
761         if ($last) {
762                 $next = $last + ($poll_interval * 60);
763                 if ($next > time()) {
764                         Logger::notice('statusnet: poll intervall not reached');
765                         return;
766                 }
767         }
768         Logger::notice('statusnet: cron_start');
769
770         $pconfigs = DBA::selectToArray('pconfig', [], ['cat' => 'statusnet', 'k' => 'mirror_posts', 'v' => true]);
771         foreach ($pconfigs as $rr) {
772                 Logger::notice('statusnet: fetching for user ' . $rr['uid']);
773                 statusnet_fetchtimeline($a, $rr['uid']);
774         }
775
776         $abandon_days = intval(DI::config()->get('system', 'account_abandon_days'));
777         if ($abandon_days < 1) {
778                 $abandon_days = 0;
779         }
780
781         $abandon_limit = date(DateTimeFormat::MYSQL, time() - $abandon_days * 86400);
782
783         $pconfigs = DBA::selectToArray('pconfig', [], ['cat' => 'statusnet', 'k' => 'import', 'v' => true]);
784         foreach ($pconfigs as $rr) {
785                 if ($abandon_days != 0) {
786                         if (!DBA::exists('user', ["`uid` = ? AND `login_date` >= ?", $rr['uid'], $abandon_limit])) {
787                                 Logger::notice('abandoned account: timeline from user ' . $rr['uid'] . ' will not be imported');
788                                 continue;
789                         }
790                 }
791
792                 Logger::notice('statusnet: importing timeline from user ' . $rr['uid']);
793                 statusnet_fetchhometimeline($a, $rr["uid"], $rr["v"]);
794         }
795
796         Logger::notice('statusnet: cron_end');
797
798         DI::config()->set('statusnet', 'last_poll', time());
799 }
800
801 function statusnet_fetchtimeline(App $a, $uid)
802 {
803         $ckey    = DI::pConfig()->get($uid, 'statusnet', 'consumerkey');
804         $csecret = DI::pConfig()->get($uid, 'statusnet', 'consumersecret');
805         $api     = DI::pConfig()->get($uid, 'statusnet', 'baseapi');
806         $otoken  = DI::pConfig()->get($uid, 'statusnet', 'oauthtoken');
807         $osecret = DI::pConfig()->get($uid, 'statusnet', 'oauthsecret');
808         $lastid  = DI::pConfig()->get($uid, 'statusnet', 'lastid');
809
810         require_once 'mod/item.php';
811         //  get the application name for the SN app
812         //  1st try personal config, then system config and fallback to the
813         //  hostname of the node if neither one is set.
814         $application_name = DI::pConfig()->get($uid, 'statusnet', 'application_name');
815         if ($application_name == "") {
816                 $application_name = DI::config()->get('statusnet', 'application_name');
817         }
818         if ($application_name == "") {
819                 $application_name = DI::baseUrl()->getHostname();
820         }
821
822         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
823
824         $parameters = ["exclude_replies" => true, "trim_user" => true, "contributor_details" => false, "include_rts" => false];
825
826         $first_time = ($lastid == "");
827
828         if ($lastid <> "") {
829                 $parameters["since_id"] = $lastid;
830         }
831
832         $items = $connection->get('statuses/user_timeline', $parameters);
833
834         if (!is_array($items)) {
835                 return;
836         }
837
838         $posts = array_reverse($items);
839
840         if (count($posts)) {
841                 foreach ($posts as $post) {
842                         if ($post->id > $lastid)
843                                 $lastid = $post->id;
844
845                         if ($first_time) {
846                                 continue;
847                         }
848
849                         if ($post->source == "activity") {
850                                 continue;
851                         }
852
853                         if (!empty($post->retweeted_status)) {
854                                 continue;
855                         }
856
857                         if ($post->in_reply_to_status_id != "") {
858                                 continue;
859                         }
860
861                         if (!stristr($post->source, $application_name)) {
862                                 $_SESSION["authenticated"] = true;
863                                 $_SESSION["uid"] = $uid;
864
865                                 unset($_REQUEST);
866                                 $_REQUEST["api_source"] = true;
867                                 $_REQUEST["profile_uid"] = $uid;
868                                 //$_REQUEST["source"] = "StatusNet";
869                                 $_REQUEST["source"] = $post->source;
870                                 $_REQUEST["extid"] = Protocol::STATUSNET;
871
872                                 if (isset($post->id)) {
873                                         $_REQUEST['message_id'] = Item::newURI($uid, Protocol::STATUSNET . ":" . $post->id);
874                                 }
875
876                                 //$_REQUEST["date"] = $post->created_at;
877
878                                 $_REQUEST["title"] = "";
879
880                                 $_REQUEST["body"] = $post->text;
881                                 if (is_string($post->place->name)) {
882                                         $_REQUEST["location"] = $post->place->name;
883                                 }
884
885                                 if (is_string($post->place->full_name)) {
886                                         $_REQUEST["location"] = $post->place->full_name;
887                                 }
888
889                                 if (is_array($post->geo->coordinates)) {
890                                         $_REQUEST["coord"] = $post->geo->coordinates[0] . " " . $post->geo->coordinates[1];
891                                 }
892
893                                 if (is_array($post->coordinates->coordinates)) {
894                                         $_REQUEST["coord"] = $post->coordinates->coordinates[1] . " " . $post->coordinates->coordinates[0];
895                                 }
896
897                                 //print_r($_REQUEST);
898                                 if ($_REQUEST["body"] != "") {
899                                         Logger::notice('statusnet: posting for user ' . $uid);
900
901                                         item_post($a);
902                                 }
903                         }
904                 }
905         }
906         DI::pConfig()->set($uid, 'statusnet', 'lastid', $lastid);
907 }
908
909 function statusnet_address($contact)
910 {
911         $hostname = Strings::normaliseLink($contact->statusnet_profile_url);
912         $nickname = $contact->screen_name;
913
914         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $contact->statusnet_profile_url);
915
916         $address = $contact->screen_name . "@" . $hostname;
917
918         return $address;
919 }
920
921 function statusnet_fetch_contact($uid, $contact, $create_user)
922 {
923         if (empty($contact->statusnet_profile_url)) {
924                 return -1;
925         }
926
927         $contact_record = Contact::selectFirst([],
928                 ['alias' => Strings::normaliseLink($contact->statusnet_profile_url), 'uid' => $uid, 'network' => Protocol::STATUSNET]);
929
930         if (!DBA::isResult($contact_record) && !$create_user) {
931                 return 0;
932         }
933
934         if (DBA::isResult($contact_record) && ($contact_record["readonly"] || $contact_record["blocked"])) {
935                 Logger::info("statusnet_fetch_contact: Contact '" . $contact_record["nick"] . "' is blocked or readonly.");
936                 return -1;
937         }
938
939         if (!DBA::isResult($contact_record)) {
940                 $fields = [
941                         'uid'      => $uid,
942                         'created'  => DateTimeFormat::utcNow(),
943                         'url'      => $contact->statusnet_profile_url,
944                         'nurl'     => Strings::normaliseLink($contact->statusnet_profile_url),
945                         'addr'     => statusnet_address($contact),
946                         'alias'    => Strings::normaliseLink($contact->statusnet_profile_url),
947                         'notify'   => '',
948                         'poll'     => '',
949                         'name'     => $contact->name,
950                         'nick'     => $contact->screen_name,
951                         'photo'    => $contact->profile_image_url,
952                         'network'  => Protocol::STATUSNET,
953                         'rel'      => Contact::FRIEND,
954                         'priority' => 1,
955                         'location' => $contact->location,
956                         'about'    => $contact->description,
957                         'writable' => true,
958                         'blocked'  => false,
959                         'readonly' => false,
960                         'pending'  => false,
961                 ];
962
963                 if (!Contact::insert($fields)) {
964                         return false;
965                 }
966
967                 $contact_record = Contact::selectFirst([],
968                         ['alias' => Strings::normaliseLink($contact->statusnet_profile_url), 'uid' => $uid, 'network' => Protocol::STATUSNET]);
969                 if (!DBA::isResult($contact_record)) {
970                         return false;
971                 }
972
973                 $contact_id = $contact_record['id'];
974
975                 Group::addMember(User::getDefaultGroup($uid), $contact_id);
976
977                 $photos = Photo::importProfilePhoto($contact->profile_image_url, $uid, $contact_id);
978
979                 Contact::update(['photo' => $photos[0], 'thumb' => $photos[1],
980                         'micro' => $photos[2], 'avatar-date' => DateTimeFormat::utcNow()], ['id' => $contact_id]);
981         } else {
982                 // update profile photos once every two weeks as we have no notification of when they change.
983                 //$update_photo = (($contact_record['avatar-date'] < DateTimeFormat::convert('now -2 days', '', '', )) ? true : false);
984                 $update_photo = ($contact_record['avatar-date'] < DateTimeFormat::utc('now -12 hours'));
985
986                 // check that we have all the photos, this has been known to fail on occasion
987                 if ((!$contact_record['photo']) || (!$contact_record['thumb']) || (!$contact_record['micro']) || ($update_photo)) {
988                         Logger::info("statusnet_fetch_contact: Updating contact " . $contact->screen_name);
989
990                         $photos = Photo::importProfilePhoto($contact->profile_image_url, $uid, $contact_record['id']);
991
992                         Contact::update([
993                                 'photo' => $photos[0],
994                                 'thumb' => $photos[1],
995                                 'micro' => $photos[2],
996                                 'name-date' => DateTimeFormat::utcNow(),
997                                 'uri-date' => DateTimeFormat::utcNow(),
998                                 'avatar-date' => DateTimeFormat::utcNow(),
999                                 'url' => $contact->statusnet_profile_url,
1000                                 'nurl' => Strings::normaliseLink($contact->statusnet_profile_url),
1001                                 'addr' => statusnet_address($contact),
1002                                 'name' => $contact->name,
1003                                 'nick' => $contact->screen_name,
1004                                 'location' => $contact->location,
1005                                 'about' => $contact->description
1006                         ], ['id' => $contact_record['id']]);
1007                 }
1008         }
1009
1010         return $contact_record["id"];
1011 }
1012
1013 function statusnet_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
1014 {
1015         $ckey    = DI::pConfig()->get($uid, 'statusnet', 'consumerkey');
1016         $csecret = DI::pConfig()->get($uid, 'statusnet', 'consumersecret');
1017         $api     = DI::pConfig()->get($uid, 'statusnet', 'baseapi');
1018         $otoken  = DI::pConfig()->get($uid, 'statusnet', 'oauthtoken');
1019         $osecret = DI::pConfig()->get($uid, 'statusnet', 'oauthsecret');
1020
1021         require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'codebirdsn.php';
1022
1023         $cb = CodebirdSN::getInstance();
1024         $cb->setConsumerKey($ckey, $csecret);
1025         $cb->setToken($otoken, $osecret);
1026
1027         $self = Contact::selectFirst([], ['self' => true, 'uid' => $uid]);
1028         if (!DBA::isResult($self)) {
1029                 return;
1030         }
1031
1032         $parameters = [];
1033
1034         if ($screen_name != "") {
1035                 $parameters["screen_name"] = $screen_name;
1036         }
1037
1038         if ($user_id != "") {
1039                 $parameters["user_id"] = $user_id;
1040         }
1041
1042         // Fetching user data
1043         $user = $cb->users_show($parameters);
1044
1045         if (!is_object($user)) {
1046                 return;
1047         }
1048
1049         $contact_id = statusnet_fetch_contact($uid, $user, true);
1050
1051         return $contact_id;
1052 }
1053
1054 function statusnet_createpost(App $a, $uid, $post, $self, $create_user, $only_existing_contact)
1055 {
1056         Logger::info("statusnet_createpost: start");
1057
1058         $api = DI::pConfig()->get($uid, 'statusnet', 'baseapi');
1059         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1060
1061         $postarray = [];
1062         $postarray['network'] = Protocol::STATUSNET;
1063         $postarray['uid'] = $uid;
1064         $postarray['wall'] = 0;
1065
1066         if (!empty($post->retweeted_status)) {
1067                 $content = $post->retweeted_status;
1068                 statusnet_fetch_contact($uid, $content->user, false);
1069         } else {
1070                 $content = $post;
1071         }
1072
1073         $postarray['uri'] = $hostname . "::" . $content->id;
1074
1075         if (Post::exists(['extid' => $postarray['uri'], 'uid' => $uid])) {
1076                 return [];
1077         }
1078
1079         $contactid = 0;
1080
1081         if (!empty($content->in_reply_to_status_id)) {
1082                 $thr_parent = $hostname . "::" . $content->in_reply_to_status_id;
1083
1084                 $item = Post::selectFirst(['uri'], ['uri' => $thr_parent, 'uid' => $uid]);
1085                 if (!DBA::isResult($item)) {
1086                         $item = Post::selectFirst(['uri'], ['extid' => $thr_parent, 'uid' => $uid]);
1087                 }
1088
1089                 if (DBA::isResult($item)) {
1090                         $postarray['thr-parent'] = $item['uri'];
1091                         $postarray['object-type'] = Activity\ObjectType::COMMENT;
1092                 } else {
1093                         $postarray['object-type'] = Activity\ObjectType::NOTE;
1094                 }
1095
1096                 // Is it me?
1097                 $own_url = DI::pConfig()->get($uid, 'statusnet', 'own_url');
1098
1099                 if ($content->user->id == $own_url) {
1100                         $self = DBA::selectFirst([], ['self' => true, 'uid' => $uid]);
1101                         if (DBA::isResult($self)) {
1102                                 $contactid = $self["id"];
1103
1104                                 $postarray['owner-name'] = $self["name"];
1105                                 $postarray['owner-link'] = $self["url"];
1106                                 $postarray['owner-avatar'] = $self["photo"];
1107                         } else {
1108                                 return [];
1109                         }
1110                 }
1111                 // Don't create accounts of people who just comment something
1112                 $create_user = false;
1113         } else {
1114                 $postarray['object-type'] = Activity\ObjectType::NOTE;
1115         }
1116
1117         if ($contactid == 0) {
1118                 $contactid = statusnet_fetch_contact($uid, $post->user, $create_user);
1119                 $postarray['owner-name'] = $post->user->name;
1120                 $postarray['owner-link'] = $post->user->statusnet_profile_url;
1121                 $postarray['owner-avatar'] = $post->user->profile_image_url;
1122         }
1123         if (($contactid == 0) && !$only_existing_contact) {
1124                 $contactid = $self['id'];
1125         } elseif ($contactid <= 0) {
1126                 return [];
1127         }
1128
1129         $postarray['contact-id'] = $contactid;
1130
1131         $postarray['verb'] = Activity::POST;
1132
1133         $postarray['author-name'] = $content->user->name;
1134         $postarray['author-link'] = $content->user->statusnet_profile_url;
1135         $postarray['author-avatar'] = $content->user->profile_image_url;
1136
1137         // To-Do: Maybe unreliable? Can the api be entered without trailing "/"?
1138         $hostname = str_replace("/api/", "/notice/", DI::pConfig()->get($uid, 'statusnet', 'baseapi'));
1139
1140         $postarray['plink'] = $hostname . $content->id;
1141         $postarray['app'] = strip_tags($content->source);
1142
1143         if ($content->user->protected) {
1144                 $postarray['private'] = 1;
1145                 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1146         }
1147
1148         $postarray['body'] = HTML::toBBCode($content->statusnet_html);
1149
1150         $postarray['body'] = statusnet_convertmsg($a, $postarray['body']);
1151
1152         $postarray['created'] = DateTimeFormat::utc($content->created_at);
1153         $postarray['edited'] = DateTimeFormat::utc($content->created_at);
1154
1155         if (!empty($content->place->name)) {
1156                 $postarray["location"] = $content->place->name;
1157         }
1158
1159         if (!empty($content->place->full_name)) {
1160                 $postarray["location"] = $content->place->full_name;
1161         }
1162
1163         if (!empty($content->geo->coordinates)) {
1164                 $postarray["coord"] = $content->geo->coordinates[0] . " " . $content->geo->coordinates[1];
1165         }
1166
1167         if (!empty($content->coordinates->coordinates)) {
1168                 $postarray["coord"] = $content->coordinates->coordinates[1] . " " . $content->coordinates->coordinates[0];
1169         }
1170
1171         Logger::info("statusnet_createpost: end");
1172
1173         return $postarray;
1174 }
1175
1176 function statusnet_fetchhometimeline(App $a, $uid, $mode = 1)
1177 {
1178         $conversations = [];
1179
1180         $ckey    = DI::pConfig()->get($uid, 'statusnet', 'consumerkey');
1181         $csecret = DI::pConfig()->get($uid, 'statusnet', 'consumersecret');
1182         $api     = DI::pConfig()->get($uid, 'statusnet', 'baseapi');
1183         $otoken  = DI::pConfig()->get($uid, 'statusnet', 'oauthtoken');
1184         $osecret = DI::pConfig()->get($uid, 'statusnet', 'oauthsecret');
1185         $create_user = DI::pConfig()->get($uid, 'statusnet', 'create_user');
1186
1187         // "create_user" is deactivated, since currently you cannot add users manually by now
1188         $create_user = true;
1189
1190         Logger::info("statusnet_fetchhometimeline: Fetching for user " . $uid);
1191
1192         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1193
1194         $own_contact = statusnet_fetch_own_contact($a, $uid);
1195
1196         if (empty($own_contact)) {
1197                 return;
1198         }
1199
1200         $contact = Contact::selectFirst([], ['id' => $own_contact, 'uid' => $uid]);
1201         if (DBA::isResult($contact)) {
1202                 $nick = $contact["nick"];
1203         } else {
1204                 Logger::info("statusnet_fetchhometimeline: Own GNU Social contact not found for user " . $uid);
1205                 return;
1206         }
1207
1208         $self = Contact::selectFirst([], ['self' => true, 'uid' => $uid]);
1209         if (!DBA::isResult($self)) {
1210                 Logger::info("statusnet_fetchhometimeline: Own contact not found for user " . $uid);
1211                 return;
1212         }
1213
1214         $user = User::getById($uid);
1215         if (!DBA::isResult($user)) {
1216                 Logger::info("statusnet_fetchhometimeline: Own user not found for user " . $uid);
1217                 return;
1218         }
1219
1220         $parameters = ["exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true];
1221         //$parameters["count"] = 200;
1222
1223         if ($mode == 1) {
1224                 // Fetching timeline
1225                 $lastid = DI::pConfig()->get($uid, 'statusnet', 'lasthometimelineid');
1226                 //$lastid = 1;
1227
1228                 $first_time = ($lastid == "");
1229
1230                 if ($lastid != "") {
1231                         $parameters["since_id"] = $lastid;
1232                 }
1233
1234                 $items = $connection->get('statuses/home_timeline', $parameters);
1235
1236                 if (!is_array($items)) {
1237                         if (is_object($items) && isset($items->error)) {
1238                                 $errormsg = $items->error;
1239                         } elseif (is_object($items)) {
1240                                 $errormsg = print_r($items, true);
1241                         } elseif (is_string($items) || is_float($items) || is_int($items)) {
1242                                 $errormsg = $items;
1243                         } else {
1244                                 $errormsg = "Unknown error";
1245                         }
1246
1247                         Logger::info("statusnet_fetchhometimeline: Error fetching home timeline: " . $errormsg);
1248                         return;
1249                 }
1250
1251                 $posts = array_reverse($items);
1252
1253                 Logger::info("statusnet_fetchhometimeline: Fetching timeline for user " . $uid . " " . sizeof($posts) . " items");
1254
1255                 if (count($posts)) {
1256                         foreach ($posts as $post) {
1257
1258                                 if ($post->id > $lastid) {
1259                                         $lastid = $post->id;
1260                                 }
1261
1262                                 if ($first_time) {
1263                                         continue;
1264                                 }
1265
1266                                 if (isset($post->statusnet_conversation_id)) {
1267                                         if (!isset($conversations[$post->statusnet_conversation_id])) {
1268                                                 statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1269                                                 $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1270                                         }
1271                                 } else {
1272                                         $postarray = statusnet_createpost($a, $uid, $post, $self, $create_user, true);
1273
1274                                         if (trim($postarray['body']) == "") {
1275                                                 continue;
1276                                         }
1277
1278                                         $item = Item::insert($postarray);
1279                                         $postarray["id"] = $item;
1280
1281                                         Logger::notice('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1282                                 }
1283                         }
1284                 }
1285                 DI::pConfig()->set($uid, 'statusnet', 'lasthometimelineid', $lastid);
1286         }
1287
1288         // Fetching mentions
1289         $lastid = DI::pConfig()->get($uid, 'statusnet', 'lastmentionid');
1290         $first_time = ($lastid == "");
1291
1292         if ($lastid != "") {
1293                 $parameters["since_id"] = $lastid;
1294         }
1295
1296         $items = $connection->get('statuses/mentions_timeline', $parameters);
1297
1298         if (!is_array($items)) {
1299                 Logger::info("statusnet_fetchhometimeline: Error fetching mentions: " . print_r($items, true));
1300                 return;
1301         }
1302
1303         $posts = array_reverse($items);
1304
1305         Logger::info("statusnet_fetchhometimeline: Fetching mentions for user " . $uid . " " . sizeof($posts) . " items");
1306
1307         if (count($posts)) {
1308                 foreach ($posts as $post) {
1309                         if ($post->id > $lastid) {
1310                                 $lastid = $post->id;
1311                         }
1312
1313                         if ($first_time) {
1314                                 continue;
1315                         }
1316
1317                         $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1318
1319                         if (isset($post->statusnet_conversation_id)) {
1320                                 if (!isset($conversations[$post->statusnet_conversation_id])) {
1321                                         statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1322                                         $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1323                                 }
1324                         } else {
1325                                 if (trim($postarray['body']) == "") {
1326                                         continue;
1327                                 }
1328
1329                                 $item = Item::insert($postarray);
1330
1331                                 Logger::notice('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted mention timeline item ' . $item);
1332                         }
1333                 }
1334         }
1335
1336         DI::pConfig()->set($uid, 'statusnet', 'lastmentionid', $lastid);
1337 }
1338
1339 function statusnet_complete_conversation(App $a, $uid, $self, $create_user, $nick, $conversation)
1340 {
1341         $ckey    = DI::pConfig()->get($uid, 'statusnet', 'consumerkey');
1342         $csecret = DI::pConfig()->get($uid, 'statusnet', 'consumersecret');
1343         $api     = DI::pConfig()->get($uid, 'statusnet', 'baseapi');
1344         $otoken  = DI::pConfig()->get($uid, 'statusnet', 'oauthtoken');
1345         $osecret = DI::pConfig()->get($uid, 'statusnet', 'oauthsecret');
1346         $own_url = DI::pConfig()->get($uid, 'statusnet', 'own_url');
1347
1348         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1349
1350         $parameters["count"] = 200;
1351
1352         $items = $connection->get('statusnet/conversation/' . $conversation, $parameters);
1353         if (is_array($items)) {
1354                 $posts = array_reverse($items);
1355
1356                 foreach ($posts as $post) {
1357                         $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1358
1359                         if (empty($postarray['body'])) {
1360                                 continue;
1361                         }
1362
1363                         $item = Item::insert($postarray);
1364                         $postarray["id"] = $item;
1365
1366                         Logger::notice('statusnet_complete_conversation: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1367                 }
1368         }
1369 }
1370
1371 function statusnet_convertmsg(App $a, $body)
1372 {
1373         $body = preg_replace("=\[url\=https?://([0-9]*).([0-9]*).([0-9]*).([0-9]*)/([0-9]*)\](.*?)\[\/url\]=ism", "$1.$2.$3.$4/$5", $body);
1374
1375         $URLSearchString = "^\[\]";
1376         $links = preg_match_all("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1377
1378         $footer = "";
1379         $footerurl = "";
1380         $footerlink = "";
1381         $type = "";
1382
1383         if ($links) {
1384                 foreach ($matches AS $match) {
1385                         $search = "[url=" . $match[1] . "]" . $match[2] . "[/url]";
1386
1387                         Logger::info("statusnet_convertmsg: expanding url " . $match[1]);
1388
1389                         try {
1390                                 $expanded_url = DI::httpClient()->finalUrl($match[1]);
1391                         } catch (TransferException $exception) {
1392                                 Logger::notice('statusnet_convertmsg: Couldn\'t get final URL.', ['url' => $match[1], 'exception' => $exception]);
1393                                 $expanded_url = $match[1];
1394                         }
1395
1396                         Logger::info("statusnet_convertmsg: fetching data for " . $expanded_url);
1397
1398                         $oembed_data = OEmbed::fetchURL($expanded_url, true);
1399
1400                         Logger::info("statusnet_convertmsg: fetching data: done");
1401
1402                         if ($type == "") {
1403                                 $type = $oembed_data->type;
1404                         }
1405
1406                         if ($oembed_data->type == "video") {
1407                                 //$body = str_replace($search, "[video]".$expanded_url."[/video]", $body);
1408                                 $type = $oembed_data->type;
1409                                 $footerurl = $expanded_url;
1410                                 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1411
1412                                 $body = str_replace($search, $footerlink, $body);
1413                         } elseif (($oembed_data->type == "photo") && isset($oembed_data->url)) {
1414                                 $body = str_replace($search, "[url=" . $expanded_url . "][img]" . $oembed_data->url . "[/img][/url]", $body);
1415                         } elseif ($oembed_data->type != "link") {
1416                                 $body = str_replace($search, "[url=" . $expanded_url . "]" . $expanded_url . "[/url]", $body);
1417                         } else {
1418                                 $img_str = DI::httpClient()->fetch($expanded_url, 4);
1419
1420                                 $tempfile = tempnam(get_temppath(), "cache");
1421                                 file_put_contents($tempfile, $img_str);
1422                                 $mime = mime_content_type($tempfile);
1423                                 unlink($tempfile);
1424
1425                                 if (substr($mime, 0, 6) == "image/") {
1426                                         $type = "photo";
1427                                         $body = str_replace($search, "[img]" . $expanded_url . "[/img]", $body);
1428                                 } else {
1429                                         $type = $oembed_data->type;
1430                                         $footerurl = $expanded_url;
1431                                         $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1432
1433                                         $body = str_replace($search, $footerlink, $body);
1434                                 }
1435                         }
1436                 }
1437
1438                 if ($footerurl != "") {
1439                         $footer = "\n" . PageInfo::getFooterFromUrl($footerurl);
1440                 }
1441
1442                 if (($footerlink != "") && (trim($footer) != "")) {
1443                         $removedlink = trim(str_replace($footerlink, "", $body));
1444
1445                         if (($removedlink == "") || strstr($body, $removedlink)) {
1446                                 $body = $removedlink;
1447                         }
1448
1449                         $body .= $footer;
1450                 }
1451         }
1452
1453         return $body;
1454 }
1455
1456 function statusnet_fetch_own_contact(App $a, $uid)
1457 {
1458         $ckey    = DI::pConfig()->get($uid, 'statusnet', 'consumerkey');
1459         $csecret = DI::pConfig()->get($uid, 'statusnet', 'consumersecret');
1460         $api     = DI::pConfig()->get($uid, 'statusnet', 'baseapi');
1461         $otoken  = DI::pConfig()->get($uid, 'statusnet', 'oauthtoken');
1462         $osecret = DI::pConfig()->get($uid, 'statusnet', 'oauthsecret');
1463         $own_url = DI::pConfig()->get($uid, 'statusnet', 'own_url');
1464
1465         $contact_id = 0;
1466
1467         if ($own_url == "") {
1468                 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1469
1470                 // Fetching user data
1471                 $user = $connection->get('account/verify_credentials');
1472
1473                 if (empty($user)) {
1474                         return false;
1475                 }
1476
1477                 DI::pConfig()->set($uid, 'statusnet', 'own_url', Strings::normaliseLink($user->statusnet_profile_url));
1478
1479                 $contact_id = statusnet_fetch_contact($uid, $user, true);
1480         } else {
1481                 $contact = Contact::selectFirst([], ['uid' => $uid, 'alias' => $own_url]);
1482                 if (DBA::isResult($contact)) {
1483                         $contact_id = $contact["id"];
1484                 } else {
1485                         DI::pConfig()->delete($uid, 'statusnet', 'own_url');
1486                 }
1487         }
1488         return $contact_id;
1489 }
1490
1491 function statusnet_is_retweet(App $a, $uid, $body)
1492 {
1493         $body = trim($body);
1494
1495         // Skip if it isn't a pure repeated messages
1496         // Does it start with a share?
1497         if (strpos($body, "[share") > 0) {
1498                 return false;
1499         }
1500
1501         // Does it end with a share?
1502         if (strlen($body) > (strrpos($body, "[/share]") + 8)) {
1503                 return false;
1504         }
1505
1506         $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
1507         // Skip if there is no shared message in there
1508         if ($body == $attributes) {
1509                 return false;
1510         }
1511
1512         $link = "";
1513         preg_match("/link='(.*?)'/ism", $attributes, $matches);
1514         if (!empty($matches[1])) {
1515                 $link = $matches[1];
1516         }
1517
1518         preg_match('/link="(.*?)"/ism', $attributes, $matches);
1519         if (!empty($matches[1])) {
1520                 $link = $matches[1];
1521         }
1522
1523         $ckey    = DI::pConfig()->get($uid, 'statusnet', 'consumerkey');
1524         $csecret = DI::pConfig()->get($uid, 'statusnet', 'consumersecret');
1525         $api     = DI::pConfig()->get($uid, 'statusnet', 'baseapi');
1526         $otoken  = DI::pConfig()->get($uid, 'statusnet', 'oauthtoken');
1527         $osecret = DI::pConfig()->get($uid, 'statusnet', 'oauthsecret');
1528         $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1529
1530         $id = preg_replace("=https?://" . $hostname . "/notice/(.*)=ism", "$1", $link);
1531
1532         if ($id == $link) {
1533                 return false;
1534         }
1535
1536         Logger::info('statusnet_is_retweet: Retweeting id ' . $id . ' for user ' . $uid);
1537
1538         $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1539
1540         $result = $connection->post('statuses/retweet/' . $id);
1541
1542         Logger::info('statusnet_is_retweet: result ' . print_r($result, true));
1543
1544         return isset($result->id);
1545 }