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