4 * Name: GNU Social Connector
5 * Description: Bidirectional (posting, relaying and reading) connector for GNU Social.
7 * Author: Tobias Diekershoff <https://f.diekershoff.de/profile/tobias>
8 * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
10 * Copyright (c) 2011-2013 Tobias Diekershoff, Michael Vogel
11 * All rights reserved.
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.
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.
36 define('STATUSNET_DEFAULT_POLL_INTERVAL', 5); // given in minutes
38 require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'statusnetoauth.php';
39 require_once 'include/enotify.php';
42 use Friendica\Content\OEmbed;
43 use Friendica\Content\Text\BBCode;
44 use Friendica\Content\Text\Plaintext;
45 use Friendica\Core\Addon;
46 use Friendica\Core\Config;
47 use Friendica\Core\L10n;
48 use Friendica\Core\PConfig;
49 use Friendica\Model\GContact;
50 use Friendica\Model\Group;
51 use Friendica\Model\Item;
52 use Friendica\Model\Photo;
53 use Friendica\Model\User;
54 use Friendica\Util\Network;
56 function statusnet_install()
58 // we need some hooks, for the configuration and for sending tweets
59 Addon::registerHook('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
60 Addon::registerHook('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
61 Addon::registerHook('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
62 Addon::registerHook('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
63 Addon::registerHook('jot_networks', 'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
64 Addon::registerHook('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
65 Addon::registerHook('prepare_body', 'addon/statusnet/statusnet.php', 'statusnet_prepare_body');
66 Addon::registerHook('check_item_notification', 'addon/statusnet/statusnet.php', 'statusnet_check_item_notification');
67 logger("installed GNU Social");
70 function statusnet_uninstall()
72 Addon::unregisterHook('connector_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
73 Addon::unregisterHook('connector_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
74 Addon::unregisterHook('notifier_normal', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
75 Addon::unregisterHook('post_local', 'addon/statusnet/statusnet.php', 'statusnet_post_local');
76 Addon::unregisterHook('jot_networks', 'addon/statusnet/statusnet.php', 'statusnet_jot_nets');
77 Addon::unregisterHook('cron', 'addon/statusnet/statusnet.php', 'statusnet_cron');
78 Addon::unregisterHook('prepare_body', 'addon/statusnet/statusnet.php', 'statusnet_prepare_body');
79 Addon::unregisterHook('check_item_notification', 'addon/statusnet/statusnet.php', 'statusnet_check_item_notification');
81 // old setting - remove only
82 Addon::unregisterHook('post_local_end', 'addon/statusnet/statusnet.php', 'statusnet_post_hook');
83 Addon::unregisterHook('addon_settings', 'addon/statusnet/statusnet.php', 'statusnet_settings');
84 Addon::unregisterHook('addon_settings_post', 'addon/statusnet/statusnet.php', 'statusnet_settings_post');
87 function statusnet_check_item_notification(App $a, &$notification_data)
89 $notification_data["profiles"][] = PConfig::get($notification_data["uid"], 'statusnet', 'own_url');
92 function statusnet_jot_nets(App $a, &$b)
98 $statusnet_post = PConfig::get(local_user(), 'statusnet', 'post');
99 if (intval($statusnet_post) == 1) {
100 $statusnet_defpost = PConfig::get(local_user(), 'statusnet', 'post_by_default');
101 $selected = ((intval($statusnet_defpost) == 1) ? ' checked="checked" ' : '');
102 $b .= '<div class="profile-jot-net"><input type="checkbox" name="statusnet_enable"' . $selected . ' value="1" /> '
103 . L10n::t('Post to GNU Social') . '</div>';
107 function statusnet_settings_post(App $a, $post)
112 // don't check GNU Social settings if GNU Social submit button is not clicked
113 if (!x($_POST, 'statusnet-submit')) {
117 if (isset($_POST['statusnet-disconnect'])) {
119 * if the GNU Social-disconnect checkbox is set, clear the GNU Social configuration
121 PConfig::delete(local_user(), 'statusnet', 'consumerkey');
122 PConfig::delete(local_user(), 'statusnet', 'consumersecret');
123 PConfig::delete(local_user(), 'statusnet', 'post');
124 PConfig::delete(local_user(), 'statusnet', 'post_by_default');
125 PConfig::delete(local_user(), 'statusnet', 'oauthtoken');
126 PConfig::delete(local_user(), 'statusnet', 'oauthsecret');
127 PConfig::delete(local_user(), 'statusnet', 'baseapi');
128 PConfig::delete(local_user(), 'statusnet', 'lastid');
129 PConfig::delete(local_user(), 'statusnet', 'mirror_posts');
130 PConfig::delete(local_user(), 'statusnet', 'import');
131 PConfig::delete(local_user(), 'statusnet', 'create_user');
132 PConfig::delete(local_user(), 'statusnet', 'own_id');
134 if (isset($_POST['statusnet-preconf-apiurl'])) {
136 * If the user used one of the preconfigured GNU Social server credentials
137 * use them. All the data are available in the global config.
138 * Check the API Url never the less and blame the admin if it's not working ^^
140 $globalsn = Config::get('statusnet', 'sites');
141 foreach ($globalsn as $asn) {
142 if ($asn['apiurl'] == $_POST['statusnet-preconf-apiurl']) {
143 $apibase = $asn['apiurl'];
144 $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
145 if (strlen($c) > 0) {
146 PConfig::set(local_user(), 'statusnet', 'consumerkey', $asn['consumerkey']);
147 PConfig::set(local_user(), 'statusnet', 'consumersecret', $asn['consumersecret']);
148 PConfig::set(local_user(), 'statusnet', 'baseapi', $asn['apiurl']);
149 //PConfig::set(local_user(), 'statusnet', 'application_name', $asn['applicationname'] );
151 notice(L10n::t('Please contact your site administrator.<br />The provided API URL is not valid.') . EOL . $asn['apiurl'] . EOL);
155 goaway('settings/connectors');
157 if (isset($_POST['statusnet-consumersecret'])) {
158 // check if we can reach the API of the GNU Social server
159 // we'll check the API Version for that, if we don't get one we'll try to fix the path but will
160 // resign quickly after this one try to fix the path ;-)
161 $apibase = $_POST['statusnet-baseapi'];
162 $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
163 if (strlen($c) > 0) {
164 // ok the API path is correct, let's save the settings
165 PConfig::set(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
166 PConfig::set(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
167 PConfig::set(local_user(), 'statusnet', 'baseapi', $apibase);
168 //PConfig::set(local_user(), 'statusnet', 'application_name', $_POST['statusnet-applicationname'] );
170 // the API path is not correct, maybe missing trailing / ?
171 $apibase = $apibase . '/';
172 $c = Network::fetchUrl($apibase . 'statusnet/version.xml');
173 if (strlen($c) > 0) {
174 // ok the API path is now correct, let's save the settings
175 PConfig::set(local_user(), 'statusnet', 'consumerkey', $_POST['statusnet-consumerkey']);
176 PConfig::set(local_user(), 'statusnet', 'consumersecret', $_POST['statusnet-consumersecret']);
177 PConfig::set(local_user(), 'statusnet', 'baseapi', $apibase);
179 // still not the correct API base, let's do noting
180 notice(L10n::t('We could not contact the GNU Social API with the Path you entered.') . EOL);
183 goaway('settings/connectors');
185 if (isset($_POST['statusnet-pin'])) {
186 // if the user supplied us with a PIN from GNU Social, let the magic of OAuth happen
187 $api = PConfig::get(local_user(), 'statusnet', 'baseapi');
188 $ckey = PConfig::get(local_user(), 'statusnet', 'consumerkey');
189 $csecret = PConfig::get(local_user(), 'statusnet', 'consumersecret');
190 // the token and secret for which the PIN was generated were hidden in the settings
191 // form as token and token2, we need a new connection to GNU Social using these token
192 // and secret to request a Access Token with the PIN
193 $connection = new StatusNetOAuth($api, $ckey, $csecret, $_POST['statusnet-token'], $_POST['statusnet-token2']);
194 $token = $connection->getAccessToken($_POST['statusnet-pin']);
195 // ok, now that we have the Access Token, save them in the user config
196 PConfig::set(local_user(), 'statusnet', 'oauthtoken', $token['oauth_token']);
197 PConfig::set(local_user(), 'statusnet', 'oauthsecret', $token['oauth_token_secret']);
198 PConfig::set(local_user(), 'statusnet', 'post', 1);
199 PConfig::set(local_user(), 'statusnet', 'post_taglinks', 1);
200 // reload the Addon Settings page, if we don't do it see Bug #42
201 goaway('settings/connectors');
203 // if no PIN is supplied in the POST variables, the user has changed the setting
204 // to post a dent for every new __public__ posting to the wall
205 PConfig::set(local_user(), 'statusnet', 'post', intval($_POST['statusnet-enable']));
206 PConfig::set(local_user(), 'statusnet', 'post_by_default', intval($_POST['statusnet-default']));
207 PConfig::set(local_user(), 'statusnet', 'mirror_posts', intval($_POST['statusnet-mirror']));
208 PConfig::set(local_user(), 'statusnet', 'import', intval($_POST['statusnet-import']));
209 PConfig::set(local_user(), 'statusnet', 'create_user', intval($_POST['statusnet-create_user']));
211 if (!intval($_POST['statusnet-mirror']))
212 PConfig::delete(local_user(), 'statusnet', 'lastid');
214 info(L10n::t('GNU Social settings updated.') . EOL);
221 function statusnet_settings(App $a, &$s)
226 $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/statusnet/statusnet.css' . '" media="all" />' . "\r\n";
228 * 1) Check that we have a base api url and a consumer key & secret
229 * 2) If no OAuthtoken & stuff is present, generate button to get some
230 * allow the user to cancel the connection process at this step
231 * 3) Checkbox for "Send public notices (respect size limitation)
233 $api = PConfig::get(local_user(), 'statusnet', 'baseapi');
234 $ckey = PConfig::get(local_user(), 'statusnet', 'consumerkey');
235 $csecret = PConfig::get(local_user(), 'statusnet', 'consumersecret');
236 $otoken = PConfig::get(local_user(), 'statusnet', 'oauthtoken');
237 $osecret = PConfig::get(local_user(), 'statusnet', 'oauthsecret');
238 $enabled = PConfig::get(local_user(), 'statusnet', 'post');
239 $checked = (($enabled) ? ' checked="checked" ' : '');
240 $defenabled = PConfig::get(local_user(), 'statusnet', 'post_by_default');
241 $defchecked = (($defenabled) ? ' checked="checked" ' : '');
242 $mirrorenabled = PConfig::get(local_user(), 'statusnet', 'mirror_posts');
243 $mirrorchecked = (($mirrorenabled) ? ' checked="checked" ' : '');
244 $import = PConfig::get(local_user(), 'statusnet', 'import');
245 $importselected = ["", "", ""];
246 $importselected[$import] = ' selected="selected"';
247 //$importenabled = PConfig::get(local_user(),'statusnet','import');
248 //$importchecked = (($importenabled) ? ' checked="checked" ' : '');
249 $create_userenabled = PConfig::get(local_user(), 'statusnet', 'create_user');
250 $create_userchecked = (($create_userenabled) ? ' checked="checked" ' : '');
252 $css = (($enabled) ? '' : '-disabled');
254 $s .= '<span id="settings_statusnet_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_statusnet_expanded\'); openClose(\'settings_statusnet_inflated\');">';
255 $s .= '<img class="connector' . $css . '" src="images/gnusocial.png" /><h3 class="connector">' . L10n::t('GNU Social Import/Export/Mirror') . '</h3>';
257 $s .= '<div id="settings_statusnet_expanded" class="settings-block" style="display: none;">';
258 $s .= '<span class="fakelink" onclick="openClose(\'settings_statusnet_expanded\'); openClose(\'settings_statusnet_inflated\');">';
259 $s .= '<img class="connector' . $css . '" src="images/gnusocial.png" /><h3 class="connector">' . L10n::t('GNU Social Import/Export/Mirror') . '</h3>';
262 if ((!$ckey) && (!$csecret)) {
266 $globalsn = Config::get('statusnet', 'sites');
268 * lets check if we have one or more globally configured GNU Social
269 * server OAuth credentials in the configuration. If so offer them
270 * with a little explanation to the user as choice - otherwise
271 * ignore this option entirely.
273 if (!$globalsn == null) {
274 $s .= '<h4>' . L10n::t('Globally Available GNU Social OAuthKeys') . '</h4>';
275 $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>';
276 $s .= '<div id="statusnet-preconf-wrapper">';
277 foreach ($globalsn as $asn) {
278 $s .= '<input type="radio" name="statusnet-preconf-apiurl" value="' . $asn['apiurl'] . '">' . $asn['sitename'] . '<br />';
280 $s .= '<p></p><div class="clear"></div></div>';
281 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
283 $s .= '<h4>' . L10n::t('Provide your own OAuth Credentials') . '</h4>';
284 $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>';
285 $s .= '<div id="statusnet-consumer-wrapper">';
286 $s .= '<label id="statusnet-consumerkey-label" for="statusnet-consumerkey">' . L10n::t('OAuth Consumer Key') . '</label>';
287 $s .= '<input id="statusnet-consumerkey" type="text" name="statusnet-consumerkey" size="35" /><br />';
288 $s .= '<div class="clear"></div>';
289 $s .= '<label id="statusnet-consumersecret-label" for="statusnet-consumersecret">' . L10n::t('OAuth Consumer Secret') . '</label>';
290 $s .= '<input id="statusnet-consumersecret" type="text" name="statusnet-consumersecret" size="35" /><br />';
291 $s .= '<div class="clear"></div>';
292 $s .= '<label id="statusnet-baseapi-label" for="statusnet-baseapi">' . L10n::t("Base API Path \x28remember the trailing /\x29") . '</label>';
293 $s .= '<input id="statusnet-baseapi" type="text" name="statusnet-baseapi" size="35" /><br />';
294 $s .= '<div class="clear"></div>';
295 //$s .= '<label id="statusnet-applicationname-label" for="statusnet-applicationname">'.L10n::t('GNU Socialapplication name').'</label>';
296 //$s .= '<input id="statusnet-applicationname" type="text" name="statusnet-applicationname" size="35" /><br />';
297 $s .= '<p></p><div class="clear"></div>';
298 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
302 * ok we have a consumer key pair now look into the OAuth stuff
304 if ((!$otoken) && (!$osecret)) {
306 * the user has not yet connected the account to GNU Social
307 * get a temporary OAuth key/secret pair and display a button with
308 * which the user can request a PIN to connect the account to a
309 * account at GNU Social
311 $connection = new StatusNetOAuth($api, $ckey, $csecret);
312 $request_token = $connection->getRequestToken('oob');
313 $token = $request_token['oauth_token'];
315 * make some nice form
317 $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>';
318 $s .= '<a href="' . $connection->getAuthorizeURL($token, False) . '" target="_statusnet"><img src="addon/statusnet/signinwithstatusnet.png" alt="' . L10n::t('Log in with GNU Social') . '"></a>';
319 $s .= '<div id="statusnet-pin-wrapper">';
320 $s .= '<label id="statusnet-pin-label" for="statusnet-pin">' . L10n::t('Copy the security code from GNU Social here') . '</label>';
321 $s .= '<input id="statusnet-pin" type="text" name="statusnet-pin" />';
322 $s .= '<input id="statusnet-token" type="hidden" name="statusnet-token" value="' . $token . '" />';
323 $s .= '<input id="statusnet-token2" type="hidden" name="statusnet-token2" value="' . $request_token['oauth_token_secret'] . '" />';
324 $s .= '</div><div class="clear"></div>';
325 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
326 $s .= '<h4>' . L10n::t('Cancel Connection Process') . '</h4>';
327 $s .= '<div id="statusnet-cancel-wrapper">';
328 $s .= '<p>' . L10n::t('Current GNU Social API is') . ': ' . $api . '</p>';
329 $s .= '<label id="statusnet-cancel-label" for="statusnet-cancel">' . L10n::t('Cancel GNU Social Connection') . '</label>';
330 $s .= '<input id="statusnet-cancel" type="checkbox" name="statusnet-disconnect" value="1" />';
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>';
335 * we have an OAuth key / secret pair for the user
336 * so let's give a chance to disable the postings to GNU Social
338 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
339 $details = $connection->get('account/verify_credentials');
340 $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>';
341 $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>';
342 if ($a->user['hidewall']) {
343 $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>';
345 $s .= '<div id="statusnet-enable-wrapper">';
346 $s .= '<label id="statusnet-enable-label" for="statusnet-checkbox">' . L10n::t('Allow posting to GNU Social') . '</label>';
347 $s .= '<input id="statusnet-checkbox" type="checkbox" name="statusnet-enable" value="1" ' . $checked . '/>';
348 $s .= '<div class="clear"></div>';
349 $s .= '<label id="statusnet-default-label" for="statusnet-default">' . L10n::t('Send public postings to GNU Social by default') . '</label>';
350 $s .= '<input id="statusnet-default" type="checkbox" name="statusnet-default" value="1" ' . $defchecked . '/>';
351 $s .= '<div class="clear"></div>';
353 $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>';
354 $s .= '<input id="statusnet-mirror" type="checkbox" name="statusnet-mirror" value="1" ' . $mirrorchecked . '/>';
356 $s .= '<div class="clear"></div>';
359 $s .= '<label id="statusnet-import-label" for="statusnet-import">' . L10n::t('Import the remote timeline') . '</label>';
360 //$s .= '<input id="statusnet-import" type="checkbox" name="statusnet-import" value="1" '. $importchecked . '/>';
362 $s .= '<select name="statusnet-import" id="statusnet-import" />';
363 $s .= '<option value="0" ' . $importselected[0] . '>' . L10n::t("Disabled") . '</option>';
364 $s .= '<option value="1" ' . $importselected[1] . '>' . L10n::t("Full Timeline") . '</option>';
365 $s .= '<option value="2" ' . $importselected[2] . '>' . L10n::t("Only Mentions") . '</option>';
367 $s .= '<div class="clear"></div>';
369 $s .= '<label id="statusnet-create_user-label" for="statusnet-create_user">'.L10n::t('Automatically create contacts').'</label>';
370 $s .= '<input id="statusnet-create_user" type="checkbox" name="statusnet-create_user" value="1" '. $create_userchecked . '/>';
371 $s .= '<div class="clear"></div>';
373 $s .= '<div id="statusnet-disconnect-wrapper">';
374 $s .= '<label id="statusnet-disconnect-label" for="statusnet-disconnect">' . L10n::t('Clear OAuth configuration') . '</label>';
375 $s .= '<input id="statusnet-disconnect" type="checkbox" name="statusnet-disconnect" value="1" />';
376 $s .= '</div><div class="clear"></div>';
377 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="statusnet-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
380 $s .= '</div><div class="clear"></div>';
383 function statusnet_post_local(App $a, &$b)
389 if (!local_user() || (local_user() != $b['uid'])) {
393 $statusnet_post = PConfig::get(local_user(), 'statusnet', 'post');
394 $statusnet_enable = (($statusnet_post && x($_REQUEST, 'statusnet_enable')) ? intval($_REQUEST['statusnet_enable']) : 0);
396 // if API is used, default to the chosen settings
397 if ($b['api_source'] && intval(PConfig::get(local_user(), 'statusnet', 'post_by_default'))) {
398 $statusnet_enable = 1;
401 if (!$statusnet_enable) {
405 if (strlen($b['postopts'])) {
406 $b['postopts'] .= ',';
409 $b['postopts'] .= 'statusnet';
412 function statusnet_action(App $a, $uid, $pid, $action)
414 $api = PConfig::get($uid, 'statusnet', 'baseapi');
415 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
416 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
417 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
418 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
420 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
422 logger("statusnet_action '" . $action . "' ID: " . $pid, LOGGER_DATA);
426 $result = $connection->post("statuses/destroy/" . $pid);
429 $result = $connection->post("favorites/create/" . $pid);
432 $result = $connection->post("favorites/destroy/" . $pid);
435 logger("statusnet_action '" . $action . "' send, result: " . print_r($result, true), LOGGER_DEBUG);
438 function statusnet_post_hook(App $a, &$b)
443 if (!PConfig::get($b["uid"], 'statusnet', 'import')) {
444 if ($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))
448 $api = PConfig::get($b["uid"], 'statusnet', 'baseapi');
449 $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
451 if ($b['parent'] != $b['id']) {
452 logger("statusnet_post_hook: parameter " . print_r($b, true), LOGGER_DATA);
454 // Looking if its a reply to a GNU Social post
455 $hostlength = strlen($hostname) + 2;
456 if ((substr($b["parent-uri"], 0, $hostlength) != $hostname . "::") && (substr($b["extid"], 0, $hostlength) != $hostname . "::") && (substr($b["thr-parent"], 0, $hostlength) != $hostname . "::")) {
457 logger("statusnet_post_hook: no GNU Social post " . $b["parent"]);
461 $r = q("SELECT `item`.`author-link`, `item`.`uri`, `contact`.`nick` AS contact_nick
462 FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
463 WHERE `item`.`uri` = '%s' AND `item`.`uid` = %d LIMIT 1", dbesc($b["thr-parent"]), intval($b["uid"]));
466 logger("statusnet_post_hook: no parent found " . $b["thr-parent"]);
473 //$nickname = "@[url=".$orig_post["author-link"]."]".$orig_post["contact_nick"]."[/url]";
474 //$nicknameplain = "@".$orig_post["contact_nick"];
476 $nick = preg_replace("=https?://(.*)/(.*)=ism", "$2", $orig_post["author-link"]);
478 $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nick . "[/url]";
479 $nicknameplain = "@" . $nick;
481 logger("statusnet_post_hook: comparing " . $nickname . " and " . $nicknameplain . " with " . $b["body"], LOGGER_DEBUG);
482 if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
483 $b["body"] = $nickname . " " . $b["body"];
486 logger("statusnet_post_hook: parent found " . print_r($orig_post, true), LOGGER_DEBUG);
490 if ($b['private'] || !strstr($b['postopts'], 'statusnet')) {
494 // Dont't post if the post doesn't belong to us.
495 // This is a check for forum postings
496 $self = dba::selectFirst('contact', ['id'], ['uid' => $b['uid'], 'self' => true]);
497 if ($b['contact-id'] != $self['id']) {
502 if (($b['verb'] == ACTIVITY_POST) && $b['deleted']) {
503 statusnet_action($a, $b["uid"], substr($orig_post["uri"], $hostlength), "delete");
506 if ($b['verb'] == ACTIVITY_LIKE) {
507 logger("statusnet_post_hook: parameter 2 " . substr($b["thr-parent"], $hostlength), LOGGER_DEBUG);
509 statusnet_action($a, $b["uid"], substr($b["thr-parent"], $hostlength), "unlike");
511 statusnet_action($a, $b["uid"], substr($b["thr-parent"], $hostlength), "like");
515 if ($b['deleted'] || ($b['created'] !== $b['edited'])) {
519 // if posts comes from GNU Social don't send it back
520 if ($b['extid'] == NETWORK_STATUSNET) {
524 if ($b['app'] == "StatusNet") {
528 logger('GNU Socialpost invoked');
530 PConfig::load($b['uid'], 'statusnet');
532 $api = PConfig::get($b['uid'], 'statusnet', 'baseapi');
533 $ckey = PConfig::get($b['uid'], 'statusnet', 'consumerkey');
534 $csecret = PConfig::get($b['uid'], 'statusnet', 'consumersecret');
535 $otoken = PConfig::get($b['uid'], 'statusnet', 'oauthtoken');
536 $osecret = PConfig::get($b['uid'], 'statusnet', 'oauthsecret');
538 if ($ckey && $csecret && $otoken && $osecret) {
539 // If it's a repeated message from GNU Social then do a native retweet and exit
540 if (statusnet_is_retweet($a, $b['uid'], $b['body'])) {
544 require_once 'include/bbcode.php';
545 $dent = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
546 $max_char = $dent->get_maxlength(); // max. length for a dent
548 PConfig::set($b['uid'], 'statusnet', 'max_char', $max_char);
551 $msgarr = BBCode::toPlaintext($b, $max_char, true, 7);
552 $msg = $msgarr["text"];
554 if (($msg == "") && isset($msgarr["title"]))
555 $msg = Plaintext::shorten($msgarr["title"], $max_char - 50);
559 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
560 $msg .= " \n" . $msgarr["url"];
561 } elseif (isset($msgarr["image"]) && ($msgarr["type"] != "video")) {
562 $image = $msgarr["image"];
566 $img_str = Network::fetchUrl($image);
567 $tempfile = tempnam(get_temppath(), "cache");
568 file_put_contents($tempfile, $img_str);
569 $postdata = ["status" => $msg, "media[]" => $tempfile];
571 $postdata = ["status" => $msg];
574 // and now send it :-)
577 $postdata["in_reply_to_status_id"] = substr($orig_post["uri"], $hostlength);
578 logger('statusnet_post send reply ' . print_r($postdata, true), LOGGER_DEBUG);
581 // New code that is able to post pictures
582 require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'codebirdsn.php';
583 $cb = \CodebirdSN\CodebirdSN::getInstance();
584 $cb->setAPIEndpoint($api);
585 $cb->setConsumerKey($ckey, $csecret);
586 $cb->setToken($otoken, $osecret);
587 $result = $cb->statuses_update($postdata);
588 //$result = $dent->post('statuses/update', $postdata);
589 logger('statusnet_post send, result: ' . print_r($result, true) .
590 "\nmessage: " . $msg, LOGGER_DEBUG . "\nOriginal post: " . print_r($b, true) . "\nPost Data: " . print_r($postdata, true));
592 if ($result->source) {
593 PConfig::set($b["uid"], "statusnet", "application_name", strip_tags($result->source));
596 if ($result->error) {
597 logger('Send to GNU Social failed: "' . $result->error . '"');
598 } elseif ($iscomment) {
599 logger('statusnet_post: Update extid ' . $result->id . " for post id " . $b['id']);
600 q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
601 dbesc($hostname . "::" . $result->id),
602 dbesc($result->text),
607 if ($tempfile != "") {
613 function statusnet_addon_admin_post(App $a)
617 foreach ($_POST['sitename'] as $id => $sitename) {
618 $sitename = trim($sitename);
619 $apiurl = trim($_POST['apiurl'][$id]);
620 if (!(substr($apiurl, -1) == '/')) {
621 $apiurl = $apiurl . '/';
623 $secret = trim($_POST['secret'][$id]);
624 $key = trim($_POST['key'][$id]);
625 //$applicationname = ((x($_POST, 'applicationname')) ? notags(trim($_POST['applicationname'][$id])):'');
626 if ($sitename != "" &&
630 !x($_POST['delete'][$id])) {
633 'sitename' => $sitename,
635 'consumersecret' => $secret,
636 'consumerkey' => $key,
637 //'applicationname' => $applicationname
642 $sites = Config::set('statusnet', 'sites', $sites);
645 function statusnet_addon_admin(App $a, &$o)
647 $sites = Config::get('statusnet', 'sites');
649 if (is_array($sites)) {
650 foreach ($sites as $id => $s) {
652 'sitename' => ["sitename[$id]", "Site name", $s['sitename'], ""],
653 'apiurl' => ["apiurl[$id]", "Api url", $s['apiurl'], L10n::t("Base API Path \x28remember the trailing /\x29")],
654 'secret' => ["secret[$id]", "Secret", $s['consumersecret'], ""],
655 'key' => ["key[$id]", "Key", $s['consumerkey'], ""],
656 //'applicationname' => Array("applicationname[$id]", "Application name", $s['applicationname'], ""),
657 'delete' => ["delete[$id]", "Delete", False, "Check to delete this preset"],
661 /* empty form to add new site */
664 'sitename' => ["sitename[$id]", L10n::t("Site name"), "", ""],
665 'apiurl' => ["apiurl[$id]", "Api url", "", L10n::t("Base API Path \x28remember the trailing /\x29")],
666 'secret' => ["secret[$id]", L10n::t("Consumer Secret"), "", ""],
667 'key' => ["key[$id]", L10n::t("Consumer Key"), "", ""],
668 //'applicationname' => Array("applicationname[$id]", L10n::t("Application name"), "", ""),
671 $t = get_markup_template("admin.tpl", "addon/statusnet/");
672 $o = replace_macros($t, [
673 '$submit' => L10n::t('Save Settings'),
674 '$sites' => $sitesform,
678 function statusnet_prepare_body(App $a, &$b)
680 if ($b["item"]["network"] != NETWORK_STATUSNET) {
685 $max_char = PConfig::get(local_user(), 'statusnet', 'max_char');
686 if (intval($max_char) == 0) {
691 $item["plink"] = $a->get_baseurl() . "/display/" . $a->user["nickname"] . "/" . $item["parent"];
693 $r = q("SELECT `item`.`author-link`, `item`.`uri`, `contact`.`nick` AS contact_nick
694 FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
695 WHERE `item`.`uri` = '%s' AND `item`.`uid` = %d LIMIT 1",
696 dbesc($item["thr-parent"]),
697 intval(local_user()));
701 //$nickname = "@[url=".$orig_post["author-link"]."]".$orig_post["contact_nick"]."[/url]";
702 //$nicknameplain = "@".$orig_post["contact_nick"];
704 $nick = preg_replace("=https?://(.*)/(.*)=ism", "$2", $orig_post["author-link"]);
706 $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nick . "[/url]";
707 $nicknameplain = "@" . $nick;
709 if ((strpos($item["body"], $nickname) === false) && (strpos($item["body"], $nicknameplain) === false)) {
710 $item["body"] = $nickname . " " . $item["body"];
714 $msgarr = BBCode::toPlaintext($item, $max_char, true, 7);
715 $msg = $msgarr["text"];
717 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
718 $msg .= " " . $msgarr["url"];
721 if (isset($msgarr["image"])) {
722 $msg .= " " . $msgarr["image"];
725 $b['html'] = nl2br(htmlspecialchars($msg));
729 function statusnet_cron(App $a, $b)
731 $last = Config::get('statusnet', 'last_poll');
733 $poll_interval = intval(Config::get('statusnet', 'poll_interval'));
734 if (!$poll_interval) {
735 $poll_interval = STATUSNET_DEFAULT_POLL_INTERVAL;
739 $next = $last + ($poll_interval * 60);
740 if ($next > time()) {
741 logger('statusnet: poll intervall not reached');
745 logger('statusnet: cron_start');
747 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'statusnet' AND `k` = 'mirror_posts' AND `v` = '1' ORDER BY RAND() ");
749 foreach ($r as $rr) {
750 logger('statusnet: fetching for user ' . $rr['uid']);
751 statusnet_fetchtimeline($a, $rr['uid']);
755 $abandon_days = intval(Config::get('system', 'account_abandon_days'));
756 if ($abandon_days < 1) {
760 $abandon_limit = date("Y-m-d H:i:s", time() - $abandon_days * 86400);
762 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'statusnet' AND `k` = 'import' AND `v` ORDER BY RAND()");
764 foreach ($r as $rr) {
765 if ($abandon_days != 0) {
766 $user = q("SELECT `login_date` FROM `user` WHERE uid=%d AND `login_date` >= '%s'", $rr['uid'], $abandon_limit);
768 logger('abandoned account: timeline from user ' . $rr['uid'] . ' will not be imported');
773 logger('statusnet: importing timeline from user ' . $rr['uid']);
774 statusnet_fetchhometimeline($a, $rr["uid"], $rr["v"]);
778 logger('statusnet: cron_end');
780 Config::set('statusnet', 'last_poll', time());
783 function statusnet_fetchtimeline(App $a, $uid)
785 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
786 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
787 $api = PConfig::get($uid, 'statusnet', 'baseapi');
788 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
789 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
790 $lastid = PConfig::get($uid, 'statusnet', 'lastid');
792 require_once 'mod/item.php';
793 require_once 'include/items.php';
795 // get the application name for the SN app
796 // 1st try personal config, then system config and fallback to the
797 // hostname of the node if neither one is set.
798 $application_name = PConfig::get($uid, 'statusnet', 'application_name');
799 if ($application_name == "") {
800 $application_name = Config::get('statusnet', 'application_name');
802 if ($application_name == "") {
803 $application_name = $a->get_hostname();
806 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
808 $parameters = ["exclude_replies" => true, "trim_user" => true, "contributor_details" => false, "include_rts" => false];
810 $first_time = ($lastid == "");
813 $parameters["since_id"] = $lastid;
816 $items = $connection->get('statuses/user_timeline', $parameters);
818 if (!is_array($items)) {
822 $posts = array_reverse($items);
825 foreach ($posts as $post) {
826 if ($post->id > $lastid)
833 if ($post->source == "activity") {
837 if (is_object($post->retweeted_status)) {
841 if ($post->in_reply_to_status_id != "") {
845 if (!stristr($post->source, $application_name)) {
846 $_SESSION["authenticated"] = true;
847 $_SESSION["uid"] = $uid;
850 $_REQUEST["type"] = "wall";
851 $_REQUEST["api_source"] = true;
852 $_REQUEST["profile_uid"] = $uid;
853 //$_REQUEST["source"] = "StatusNet";
854 $_REQUEST["source"] = $post->source;
855 $_REQUEST["extid"] = NETWORK_STATUSNET;
857 if (isset($post->id)) {
858 $_REQUEST['message_id'] = item_new_uri($a->get_hostname(), $uid, NETWORK_STATUSNET . ":" . $post->id);
861 //$_REQUEST["date"] = $post->created_at;
863 $_REQUEST["title"] = "";
865 $_REQUEST["body"] = add_page_info_to_body($post->text, true);
866 if (is_string($post->place->name)) {
867 $_REQUEST["location"] = $post->place->name;
870 if (is_string($post->place->full_name)) {
871 $_REQUEST["location"] = $post->place->full_name;
874 if (is_array($post->geo->coordinates)) {
875 $_REQUEST["coord"] = $post->geo->coordinates[0] . " " . $post->geo->coordinates[1];
878 if (is_array($post->coordinates->coordinates)) {
879 $_REQUEST["coord"] = $post->coordinates->coordinates[1] . " " . $post->coordinates->coordinates[0];
882 //print_r($_REQUEST);
883 if ($_REQUEST["body"] != "") {
884 logger('statusnet: posting for user ' . $uid);
891 PConfig::set($uid, 'statusnet', 'lastid', $lastid);
894 function statusnet_address($contact)
896 $hostname = normalise_link($contact->statusnet_profile_url);
897 $nickname = $contact->screen_name;
899 $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $contact->statusnet_profile_url);
901 $address = $contact->screen_name . "@" . $hostname;
906 function statusnet_fetch_contact($uid, $contact, $create_user)
908 if ($contact->statusnet_profile_url == "") {
912 GContact::update(["url" => $contact->statusnet_profile_url,
913 "network" => NETWORK_STATUSNET, "photo" => $contact->profile_image_url,
914 "name" => $contact->name, "nick" => $contact->screen_name,
915 "location" => $contact->location, "about" => $contact->description,
916 "addr" => statusnet_address($contact), "generation" => 3]);
918 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' AND `network` = '%s'LIMIT 1", intval($uid), dbesc(normalise_link($contact->statusnet_profile_url)), dbesc(NETWORK_STATUSNET));
920 if (!count($r) && !$create_user) {
924 if (count($r) && ($r[0]["readonly"] || $r[0]["blocked"])) {
925 logger("statusnet_fetch_contact: Contact '" . $r[0]["nick"] . "' is blocked or readonly.", LOGGER_DEBUG);
930 // create contact record
931 q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
932 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
933 `location`, `about`, `writable`, `blocked`, `readonly`, `pending` )
934 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, 0, 0, 0 ) ",
936 dbesc(datetime_convert()),
937 dbesc($contact->statusnet_profile_url),
938 dbesc(normalise_link($contact->statusnet_profile_url)),
939 dbesc(statusnet_address($contact)),
940 dbesc(normalise_link($contact->statusnet_profile_url)),
943 dbesc($contact->name),
944 dbesc($contact->screen_name),
945 dbesc($contact->profile_image_url),
946 dbesc(NETWORK_STATUSNET),
947 intval(CONTACT_IS_FRIEND),
949 dbesc($contact->location),
950 dbesc($contact->description),
954 $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d AND `network` = '%s' LIMIT 1",
955 dbesc($contact->statusnet_profile_url),
957 dbesc(NETWORK_STATUSNET));
963 $contact_id = $r[0]['id'];
965 Group::addMember(User::getDefaultGroup($uid), $contact_id);
967 $photos = Photo::importProfilePhoto($contact->profile_image_url, $uid, $contact_id);
969 q("UPDATE `contact` SET `photo` = '%s',
977 dbesc(datetime_convert()),
981 // update profile photos once every two weeks as we have no notification of when they change.
982 //$update_photo = (($r[0]['avatar-date'] < datetime_convert('','','now -2 days')) ? true : false);
983 $update_photo = ($r[0]['avatar-date'] < datetime_convert('', '', 'now -12 hours'));
985 // check that we have all the photos, this has been known to fail on occasion
986 if ((!$r[0]['photo']) || (!$r[0]['thumb']) || (!$r[0]['micro']) || ($update_photo)) {
987 logger("statusnet_fetch_contact: Updating contact " . $contact->screen_name, LOGGER_DEBUG);
989 $photos = Photo::importProfilePhoto($contact->profile_image_url, $uid, $r[0]['id']);
991 q("UPDATE `contact` SET `photo` = '%s',
996 `avatar-date` = '%s',
1008 dbesc(datetime_convert()),
1009 dbesc(datetime_convert()),
1010 dbesc(datetime_convert()),
1011 dbesc($contact->statusnet_profile_url),
1012 dbesc(normalise_link($contact->statusnet_profile_url)),
1013 dbesc(statusnet_address($contact)),
1014 dbesc($contact->name),
1015 dbesc($contact->screen_name),
1016 dbesc($contact->location),
1017 dbesc($contact->description),
1026 function statusnet_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
1028 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1029 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1030 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1031 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1032 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1034 require_once __DIR__ . DIRECTORY_SEPARATOR . 'library' . DIRECTORY_SEPARATOR . 'codebirdsn.php';
1036 $cb = \CodebirdSN\CodebirdSN::getInstance();
1037 $cb->setConsumerKey($ckey, $csecret);
1038 $cb->setToken($otoken, $osecret);
1040 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1051 if ($screen_name != "") {
1052 $parameters["screen_name"] = $screen_name;
1055 if ($user_id != "") {
1056 $parameters["user_id"] = $user_id;
1059 // Fetching user data
1060 $user = $cb->users_show($parameters);
1062 if (!is_object($user)) {
1066 $contact_id = statusnet_fetch_contact($uid, $user, true);
1071 function statusnet_createpost(App $a, $uid, $post, $self, $create_user, $only_existing_contact)
1073 require_once "include/html2bbcode.php";
1075 logger("statusnet_createpost: start", LOGGER_DEBUG);
1077 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1078 $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1081 $postarray['network'] = NETWORK_STATUSNET;
1082 $postarray['gravity'] = 0;
1083 $postarray['uid'] = $uid;
1084 $postarray['wall'] = 0;
1086 if (is_object($post->retweeted_status)) {
1087 $content = $post->retweeted_status;
1088 statusnet_fetch_contact($uid, $content->user, false);
1093 $postarray['uri'] = $hostname . "::" . $content->id;
1095 $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1096 dbesc($postarray['uri']),
1106 if ($content->in_reply_to_status_id != "") {
1108 $parent = $hostname . "::" . $content->in_reply_to_status_id;
1110 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1115 $postarray['thr-parent'] = $r[0]["uri"];
1116 $postarray['parent-uri'] = $r[0]["parent-uri"];
1117 $postarray['parent'] = $r[0]["parent"];
1118 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1120 $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1125 $postarray['thr-parent'] = $r[0]['uri'];
1126 $postarray['parent-uri'] = $r[0]['parent-uri'];
1127 $postarray['parent'] = $r[0]['parent'];
1128 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1130 $postarray['thr-parent'] = $postarray['uri'];
1131 $postarray['parent-uri'] = $postarray['uri'];
1132 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1137 $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1139 if ($content->user->id == $own_url) {
1140 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1144 $contactid = $r[0]["id"];
1146 $postarray['owner-name'] = $r[0]["name"];
1147 $postarray['owner-link'] = $r[0]["url"];
1148 $postarray['owner-avatar'] = $r[0]["photo"];
1153 // Don't create accounts of people who just comment something
1154 $create_user = false;
1156 $postarray['parent-uri'] = $postarray['uri'];
1157 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1160 if ($contactid == 0) {
1161 $contactid = statusnet_fetch_contact($uid, $post->user, $create_user);
1162 $postarray['owner-name'] = $post->user->name;
1163 $postarray['owner-link'] = $post->user->statusnet_profile_url;
1164 $postarray['owner-avatar'] = $post->user->profile_image_url;
1166 if (($contactid == 0) && !$only_existing_contact) {
1167 $contactid = $self['id'];
1168 } elseif ($contactid <= 0) {
1172 $postarray['contact-id'] = $contactid;
1174 $postarray['verb'] = ACTIVITY_POST;
1176 $postarray['author-name'] = $content->user->name;
1177 $postarray['author-link'] = $content->user->statusnet_profile_url;
1178 $postarray['author-avatar'] = $content->user->profile_image_url;
1180 // To-Do: Maybe unreliable? Can the api be entered without trailing "/"?
1181 $hostname = str_replace("/api/", "/notice/", PConfig::get($uid, 'statusnet', 'baseapi'));
1183 $postarray['plink'] = $hostname . $content->id;
1184 $postarray['app'] = strip_tags($content->source);
1186 if ($content->user->protected) {
1187 $postarray['private'] = 1;
1188 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1191 $postarray['body'] = html2bbcode($content->statusnet_html);
1193 $converted = statusnet_convertmsg($a, $postarray['body'], false);
1194 $postarray['body'] = $converted["body"];
1195 $postarray['tag'] = $converted["tags"];
1197 $postarray['created'] = datetime_convert('UTC', 'UTC', $content->created_at);
1198 $postarray['edited'] = datetime_convert('UTC', 'UTC', $content->created_at);
1200 if (is_string($content->place->name)) {
1201 $postarray["location"] = $content->place->name;
1204 if (is_string($content->place->full_name)) {
1205 $postarray["location"] = $content->place->full_name;
1208 if (is_array($content->geo->coordinates)) {
1209 $postarray["coord"] = $content->geo->coordinates[0] . " " . $content->geo->coordinates[1];
1212 if (is_array($content->coordinates->coordinates)) {
1213 $postarray["coord"] = $content->coordinates->coordinates[1] . " " . $content->coordinates->coordinates[0];
1216 logger("statusnet_createpost: end", LOGGER_DEBUG);
1221 function statusnet_checknotification(App $a, $uid, $own_url, $top_item, $postarray)
1223 // This function necer worked and need cleanup
1224 $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
1228 if (!count($user)) {
1233 if (link_compare($user[0]["url"], $postarray['author-link'])) {
1237 $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1242 if (!count($own_user)) {
1246 // Is it me from GNU Social?
1247 if (link_compare($own_user[0]["url"], $postarray['author-link'])) {
1251 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
1252 dbesc($postarray['parent-uri']),
1256 if (count($myconv)) {
1257 foreach ($myconv as $conv) {
1258 // now if we find a match, it means we're in this conversation
1259 if (!link_compare($conv['author-link'], $user[0]["url"]) && !link_compare($conv['author-link'], $own_user[0]["url"])) {
1263 require_once 'include/enotify.php';
1265 $conv_parent = $conv['parent'];
1268 'type' => NOTIFY_COMMENT,
1269 'notify_flags' => $user[0]['notify-flags'],
1270 'language' => $user[0]['language'],
1271 'to_name' => $user[0]['username'],
1272 'to_email' => $user[0]['email'],
1273 'uid' => $user[0]['uid'],
1274 'item' => $postarray,
1275 'link' => $a->get_baseurl() . '/display/' . urlencode(Item::getGuidById($top_item)),
1276 'source_name' => $postarray['author-name'],
1277 'source_link' => $postarray['author-link'],
1278 'source_photo' => $postarray['author-avatar'],
1279 'verb' => ACTIVITY_POST,
1281 'parent' => $conv_parent,
1284 // only send one notification
1290 function statusnet_fetchhometimeline(App $a, $uid, $mode = 1)
1292 $conversations = [];
1294 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1295 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1296 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1297 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1298 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1299 $create_user = PConfig::get($uid, 'statusnet', 'create_user');
1301 // "create_user" is deactivated, since currently you cannot add users manually by now
1302 $create_user = true;
1304 logger("statusnet_fetchhometimeline: Fetching for user " . $uid, LOGGER_DEBUG);
1306 require_once 'include/items.php';
1308 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1310 $own_contact = statusnet_fetch_own_contact($a, $uid);
1312 $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1313 intval($own_contact),
1317 $nick = $r[0]["nick"];
1319 logger("statusnet_fetchhometimeline: Own GNU Social contact not found for user " . $uid, LOGGER_DEBUG);
1323 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1329 logger("statusnet_fetchhometimeline: Own contact not found for user " . $uid, LOGGER_DEBUG);
1333 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1336 logger("statusnet_fetchhometimeline: Own user not found for user " . $uid, LOGGER_DEBUG);
1340 $parameters = ["exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true];
1341 //$parameters["count"] = 200;
1344 // Fetching timeline
1345 $lastid = PConfig::get($uid, 'statusnet', 'lasthometimelineid');
1348 $first_time = ($lastid == "");
1350 if ($lastid != "") {
1351 $parameters["since_id"] = $lastid;
1354 $items = $connection->get('statuses/home_timeline', $parameters);
1356 if (!is_array($items)) {
1357 if (is_object($items) && isset($items->error)) {
1358 $errormsg = $items->error;
1359 } elseif (is_object($items)) {
1360 $errormsg = print_r($items, true);
1361 } elseif (is_string($items) || is_float($items) || is_int($items)) {
1364 $errormsg = "Unknown error";
1367 logger("statusnet_fetchhometimeline: Error fetching home timeline: " . $errormsg, LOGGER_DEBUG);
1371 $posts = array_reverse($items);
1373 logger("statusnet_fetchhometimeline: Fetching timeline for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1375 if (count($posts)) {
1376 foreach ($posts as $post) {
1378 if ($post->id > $lastid) {
1379 $lastid = $post->id;
1386 if (isset($post->statusnet_conversation_id)) {
1387 if (!isset($conversations[$post->statusnet_conversation_id])) {
1388 statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1389 $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1392 $postarray = statusnet_createpost($a, $uid, $post, $self, $create_user, true);
1394 if (trim($postarray['body']) == "") {
1398 $item = Item::insert($postarray);
1399 $postarray["id"] = $item;
1401 logger('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1403 if ($item && !function_exists("check_item_notification")) {
1404 statusnet_checknotification($a, $uid, $nick, $item, $postarray);
1409 PConfig::set($uid, 'statusnet', 'lasthometimelineid', $lastid);
1412 // Fetching mentions
1413 $lastid = PConfig::get($uid, 'statusnet', 'lastmentionid');
1414 $first_time = ($lastid == "");
1416 if ($lastid != "") {
1417 $parameters["since_id"] = $lastid;
1420 $items = $connection->get('statuses/mentions_timeline', $parameters);
1422 if (!is_array($items)) {
1423 logger("statusnet_fetchhometimeline: Error fetching mentions: " . print_r($items, true), LOGGER_DEBUG);
1427 $posts = array_reverse($items);
1429 logger("statusnet_fetchhometimeline: Fetching mentions for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1431 if (count($posts)) {
1432 foreach ($posts as $post) {
1433 if ($post->id > $lastid) {
1434 $lastid = $post->id;
1441 $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1443 if (isset($post->statusnet_conversation_id)) {
1444 if (!isset($conversations[$post->statusnet_conversation_id])) {
1445 statusnet_complete_conversation($a, $uid, $self, $create_user, $nick, $post->statusnet_conversation_id);
1446 $conversations[$post->statusnet_conversation_id] = $post->statusnet_conversation_id;
1449 if (trim($postarray['body']) == "") {
1453 $item = Item::insert($postarray);
1454 $postarray["id"] = $item;
1456 logger('statusnet_fetchhometimeline: User ' . $self["nick"] . ' posted mention timeline item ' . $item);
1458 if ($item && function_exists("check_item_notification")) {
1459 check_item_notification($item, $uid, NOTIFY_TAGSELF);
1463 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1464 dbesc($postarray['uri']),
1468 $item = $r[0]['id'];
1469 $parent_id = $r[0]['parent'];
1472 if (($item != 0) && !function_exists("check_item_notification")) {
1473 require_once 'include/enotify.php';
1475 'type' => NOTIFY_TAGSELF,
1476 'notify_flags' => $u[0]['notify-flags'],
1477 'language' => $u[0]['language'],
1478 'to_name' => $u[0]['username'],
1479 'to_email' => $u[0]['email'],
1480 'uid' => $u[0]['uid'],
1481 'item' => $postarray,
1482 'link' => $a->get_baseurl() . '/display/' . urlencode(Item::getGuidById($item)),
1483 'source_name' => $postarray['author-name'],
1484 'source_link' => $postarray['author-link'],
1485 'source_photo' => $postarray['author-avatar'],
1486 'verb' => ACTIVITY_TAG,
1488 'parent' => $parent_id,
1494 PConfig::set($uid, 'statusnet', 'lastmentionid', $lastid);
1497 function statusnet_complete_conversation(App $a, $uid, $self, $create_user, $nick, $conversation)
1499 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1500 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1501 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1502 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1503 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1504 $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1506 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1508 $parameters["count"] = 200;
1510 $items = $connection->get('statusnet/conversation/' . $conversation, $parameters);
1511 if (is_array($items)) {
1512 $posts = array_reverse($items);
1514 foreach ($posts as $post) {
1515 $postarray = statusnet_createpost($a, $uid, $post, $self, false, false);
1517 if (trim($postarray['body']) == "") {
1521 $item = Item::insert($postarray);
1522 $postarray["id"] = $item;
1524 logger('statusnet_complete_conversation: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1526 if ($item && !function_exists("check_item_notification")) {
1527 statusnet_checknotification($a, $uid, $nick, $item, $postarray);
1533 function statusnet_convertmsg(App $a, $body, $no_tags = false)
1535 require_once "include/items.php";
1537 $body = preg_replace("=\[url\=https?://([0-9]*).([0-9]*).([0-9]*).([0-9]*)/([0-9]*)\](.*?)\[\/url\]=ism", "$1.$2.$3.$4/$5", $body);
1539 $URLSearchString = "^\[\]";
1540 $links = preg_match_all("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1548 foreach ($matches AS $match) {
1549 $search = "[url=" . $match[1] . "]" . $match[2] . "[/url]";
1551 logger("statusnet_convertmsg: expanding url " . $match[1], LOGGER_DEBUG);
1553 $expanded_url = Network::finalUrl($match[1]);
1555 logger("statusnet_convertmsg: fetching data for " . $expanded_url, LOGGER_DEBUG);
1557 $oembed_data = OEmbed::fetchURL($expanded_url, true);
1559 logger("statusnet_convertmsg: fetching data: done", LOGGER_DEBUG);
1562 $type = $oembed_data->type;
1565 if ($oembed_data->type == "video") {
1566 //$body = str_replace($search, "[video]".$expanded_url."[/video]", $body);
1567 $type = $oembed_data->type;
1568 $footerurl = $expanded_url;
1569 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1571 $body = str_replace($search, $footerlink, $body);
1572 } elseif (($oembed_data->type == "photo") && isset($oembed_data->url) && !$dontincludemedia) {
1573 $body = str_replace($search, "[url=" . $expanded_url . "][img]" . $oembed_data->url . "[/img][/url]", $body);
1574 } elseif ($oembed_data->type != "link") {
1575 $body = str_replace($search, "[url=" . $expanded_url . "]" . $expanded_url . "[/url]", $body);
1577 $img_str = Network::fetchUrl($expanded_url, true, $redirects, 4);
1579 $tempfile = tempnam(get_temppath(), "cache");
1580 file_put_contents($tempfile, $img_str);
1581 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
1584 if (substr($mime, 0, 6) == "image/") {
1586 $body = str_replace($search, "[img]" . $expanded_url . "[/img]", $body);
1588 $type = $oembed_data->type;
1589 $footerurl = $expanded_url;
1590 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1592 $body = str_replace($search, $footerlink, $body);
1597 if ($footerurl != "") {
1598 $footer = add_page_info($footerurl);
1601 if (($footerlink != "") && (trim($footer) != "")) {
1602 $removedlink = trim(str_replace($footerlink, "", $body));
1604 if (($removedlink == "") || strstr($body, $removedlink)) {
1605 $body = $removedlink;
1613 return ["body" => $body, "tags" => ""];
1618 $cnt = preg_match_all("/([!#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
1620 foreach ($matches as $mtch) {
1621 if (strlen($str_tags)) {
1625 if ($mtch[1] == "#") {
1626 // Replacing the hash tags that are directed to the GNU Social server with internal links
1627 $snhash = "#[url=" . $mtch[2] . "]" . $mtch[3] . "[/url]";
1628 $frdchash = '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($mtch[3]) . ']' . $mtch[3] . '[/url]';
1629 $body = str_replace($snhash, $frdchash, $body);
1631 $str_tags .= $frdchash;
1633 $str_tags .= "@[url=" . $mtch[2] . "]" . $mtch[3] . "[/url]";
1636 // There is a problem with links with to GNU Social groups, so these links are stored with "@" like friendica groups
1637 //$str_tags .= $mtch[1]."[url=".$mtch[2]."]".$mtch[3]."[/url]";
1641 return ["body" => $body, "tags" => $str_tags];
1644 function statusnet_fetch_own_contact(App $a, $uid)
1646 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1647 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1648 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1649 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1650 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1651 $own_url = PConfig::get($uid, 'statusnet', 'own_url');
1655 if ($own_url == "") {
1656 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1658 // Fetching user data
1659 $user = $connection->get('account/verify_credentials');
1661 PConfig::set($uid, 'statusnet', 'own_url', normalise_link($user->statusnet_profile_url));
1663 $contact_id = statusnet_fetch_contact($uid, $user, true);
1665 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1666 intval($uid), dbesc($own_url));
1668 $contact_id = $r[0]["id"];
1670 PConfig::delete($uid, 'statusnet', 'own_url');
1676 function statusnet_is_retweet(App $a, $uid, $body)
1678 $body = trim($body);
1680 // Skip if it isn't a pure repeated messages
1681 // Does it start with a share?
1682 if (strpos($body, "[share") > 0) {
1686 // Does it end with a share?
1687 if (strlen($body) > (strrpos($body, "[/share]") + 8)) {
1691 $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
1692 // Skip if there is no shared message in there
1693 if ($body == $attributes) {
1698 preg_match("/link='(.*?)'/ism", $attributes, $matches);
1699 if ($matches[1] != "") {
1700 $link = $matches[1];
1703 preg_match('/link="(.*?)"/ism', $attributes, $matches);
1704 if ($matches[1] != "") {
1705 $link = $matches[1];
1708 $ckey = PConfig::get($uid, 'statusnet', 'consumerkey');
1709 $csecret = PConfig::get($uid, 'statusnet', 'consumersecret');
1710 $api = PConfig::get($uid, 'statusnet', 'baseapi');
1711 $otoken = PConfig::get($uid, 'statusnet', 'oauthtoken');
1712 $osecret = PConfig::get($uid, 'statusnet', 'oauthsecret');
1713 $hostname = preg_replace("=https?://([\w\.]*)/.*=ism", "$1", $api);
1715 $id = preg_replace("=https?://" . $hostname . "/notice/(.*)=ism", "$1", $link);
1721 logger('statusnet_is_retweet: Retweeting id ' . $id . ' for user ' . $uid, LOGGER_DEBUG);
1723 $connection = new StatusNetOAuth($api, $ckey, $csecret, $otoken, $osecret);
1725 $result = $connection->post('statuses/retweet/' . $id);
1727 logger('statusnet_is_retweet: result ' . print_r($result, true), LOGGER_DEBUG);
1729 return isset($result->id);