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