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