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