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