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