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