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