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