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