]> git.mxchange.org Git - friendica-addons.git/blob - twitter/twitter.php
Update Twitter addon
[friendica-addons.git] / twitter / twitter.php
1 <?php
2 /**
3  * Name: Twitter Connector
4  * Description: Bidirectional (posting, relaying and reading) connector for Twitter.
5  * Version: 1.0.4
6  * Author: Tobias Diekershoff <https://f.diekershoff.de/profile/tobias>
7  * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
8  *
9  * Copyright (c) 2011-2013 Tobias Diekershoff, Michael Vogel
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions are met:
14  *    * Redistributions of source code must retain the above copyright notice,
15  *     this list of conditions and the following disclaimer.
16  *    * Redistributions in binary form must reproduce the above
17  *    * copyright notice, this list of conditions and the following disclaimer in
18  *      the documentation and/or other materials provided with the distribution.
19  *    * Neither the name of the <organization> nor the names of its contributors
20  *      may be used to endorse or promote products derived from this software
21  *      without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26  * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT,
27  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
31  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
32  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  *
34  */
35 /*   Twitter Addon for Friendica
36  *
37  *   Author: Tobias Diekershoff
38  *           tobias.diekershoff@gmx.net
39  *
40  *   License:3-clause BSD license
41  *
42  *   Configuration:
43  *     To use this addon you need a OAuth Consumer key pair (key & secret)
44  *     you can get it from Twitter at https://twitter.com/apps
45  *
46  *     Register your Friendica site as "Client" application with "Read & Write" access
47  *     we do not need "Twitter as login". When you've registered the app you get the
48  *     OAuth Consumer key and secret pair for your application/site.
49  *
50  *     Add this key pair to your global .htconfig.php or use the admin panel.
51  *
52  *     $a->config['twitter']['consumerkey'] = 'your consumer_key here';
53  *     $a->config['twitter']['consumersecret'] = 'your consumer_secret here';
54  *
55  *     To activate the addon itself add it to the $a->config['system']['addon']
56  *     setting. After this, your user can configure their Twitter account settings
57  *     from "Settings -> Addon Settings".
58  *
59  *     Requirements: PHP5, curl [Slinky library]
60  */
61
62 use Abraham\TwitterOAuth\TwitterOAuth;
63 use Friendica\App;
64 use Friendica\Content\OEmbed;
65 use Friendica\Content\Text\BBCode;
66 use Friendica\Content\Text\Plaintext;
67 use Friendica\Core\Addon;
68 use Friendica\Core\Config;
69 use Friendica\Core\L10n;
70 use Friendica\Core\PConfig;
71 use Friendica\Core\Worker;
72 use Friendica\Model\GContact;
73 use Friendica\Model\Group;
74 use Friendica\Model\Photo;
75 use Friendica\Model\Queue;
76 use Friendica\Model\User;
77 use Friendica\Object\Image;
78 use Friendica\Util\Network;
79
80 require_once 'boot.php';
81 require_once 'include/dba.php';
82 require_once 'include/enotify.php';
83 require_once 'include/text.php';
84
85 require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
86
87 define('TWITTER_DEFAULT_POLL_INTERVAL', 5); // given in minutes
88
89 function twitter_install()
90 {
91         //  we need some hooks, for the configuration and for sending tweets
92         Addon::registerHook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
93         Addon::registerHook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
94         Addon::registerHook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
95         Addon::registerHook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
96         Addon::registerHook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
97         Addon::registerHook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
98         Addon::registerHook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
99         Addon::registerHook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
100         Addon::registerHook('expire', 'addon/twitter/twitter.php', 'twitter_expire');
101         Addon::registerHook('prepare_body', 'addon/twitter/twitter.php', 'twitter_prepare_body');
102         Addon::registerHook('check_item_notification', 'addon/twitter/twitter.php', 'twitter_check_item_notification');
103         logger("installed twitter");
104 }
105
106 function twitter_uninstall()
107 {
108         Addon::unregisterHook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
109         Addon::unregisterHook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
110         Addon::unregisterHook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
111         Addon::unregisterHook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
112         Addon::unregisterHook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
113         Addon::unregisterHook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
114         Addon::unregisterHook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
115         Addon::unregisterHook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
116         Addon::unregisterHook('expire', 'addon/twitter/twitter.php', 'twitter_expire');
117         Addon::unregisterHook('prepare_body', 'addon/twitter/twitter.php', 'twitter_prepare_body');
118         Addon::unregisterHook('check_item_notification', 'addon/twitter/twitter.php', 'twitter_check_item_notification');
119
120         // old setting - remove only
121         Addon::unregisterHook('post_local_end', 'addon/twitter/twitter.php', 'twitter_post_hook');
122         Addon::unregisterHook('addon_settings', 'addon/twitter/twitter.php', 'twitter_settings');
123         Addon::unregisterHook('addon_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
124 }
125
126 function twitter_check_item_notification(App $a, &$notification_data)
127 {
128         $own_id = PConfig::get($notification_data["uid"], 'twitter', 'own_id');
129
130         $own_user = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
131                         intval($notification_data["uid"]),
132                         dbesc("twitter::".$own_id)
133         );
134
135         if ($own_user) {
136                 $notification_data["profiles"][] = $own_user[0]["url"];
137         }
138 }
139
140 function twitter_follow(App $a, &$contact)
141 {
142         logger("twitter_follow: Check if contact is twitter contact. " . $contact["url"], LOGGER_DEBUG);
143
144         if (!strstr($contact["url"], "://twitter.com") && !strstr($contact["url"], "@twitter.com")) {
145                 return;
146         }
147
148         // contact seems to be a twitter contact, so continue
149         $nickname = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $contact["url"]);
150         $nickname = str_replace("@twitter.com", "", $nickname);
151
152         $uid = $a->user["uid"];
153
154         $ckey    = Config::get('twitter', 'consumerkey');
155         $csecret = Config::get('twitter', 'consumersecret');
156         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
157         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
158
159         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
160         $connection->post('friendships/create', ['screen_name' => $nickname]);
161
162         twitter_fetchuser($a, $uid, $nickname);
163
164         $r = q("SELECT name,nick,url,addr,batch,notify,poll,request,confirm,poco,photo,priority,network,alias,pubkey
165                 FROM `contact` WHERE `uid` = %d AND `nick` = '%s'",
166                                 intval($uid),
167                                 dbesc($nickname));
168         if (count($r)) {
169                 $contact["contact"] = $r[0];
170         }
171 }
172
173 function twitter_jot_nets(App $a, &$b)
174 {
175         if (!local_user()) {
176                 return;
177         }
178
179         $tw_post = PConfig::get(local_user(), 'twitter', 'post');
180         if (intval($tw_post) == 1) {
181                 $tw_defpost = PConfig::get(local_user(), 'twitter', 'post_by_default');
182                 $selected = ((intval($tw_defpost) == 1) ? ' checked="checked" ' : '');
183                 $b .= '<div class="profile-jot-net"><input type="checkbox" name="twitter_enable"' . $selected . ' value="1" /> '
184                         . L10n::t('Post to Twitter') . '</div>';
185         }
186 }
187
188 function twitter_settings_post(App $a, $post)
189 {
190         if (!local_user()) {
191                 return;
192         }
193         // don't check twitter settings if twitter submit button is not clicked
194         if (!x($_POST, 'twitter-submit')) {
195                 return;
196         }
197
198         if (isset($_POST['twitter-disconnect'])) {
199                 /*               * *
200                  * if the twitter-disconnect checkbox is set, clear the OAuth key/secret pair
201                  * from the user configuration
202                  */
203                 PConfig::delete(local_user(), 'twitter', 'consumerkey');
204                 PConfig::delete(local_user(), 'twitter', 'consumersecret');
205                 PConfig::delete(local_user(), 'twitter', 'oauthtoken');
206                 PConfig::delete(local_user(), 'twitter', 'oauthsecret');
207                 PConfig::delete(local_user(), 'twitter', 'post');
208                 PConfig::delete(local_user(), 'twitter', 'post_by_default');
209                 PConfig::delete(local_user(), 'twitter', 'lastid');
210                 PConfig::delete(local_user(), 'twitter', 'mirror_posts');
211                 PConfig::delete(local_user(), 'twitter', 'import');
212                 PConfig::delete(local_user(), 'twitter', 'create_user');
213                 PConfig::delete(local_user(), 'twitter', 'own_id');
214         } else {
215                 if (isset($_POST['twitter-pin'])) {
216                         //  if the user supplied us with a PIN from Twitter, let the magic of OAuth happen
217                         logger('got a Twitter PIN');
218                         $ckey    = Config::get('twitter', 'consumerkey');
219                         $csecret = Config::get('twitter', 'consumersecret');
220                         //  the token and secret for which the PIN was generated were hidden in the settings
221                         //  form as token and token2, we need a new connection to Twitter using these token
222                         //  and secret to request a Access Token with the PIN
223                         $connection = new TwitterOAuth($ckey, $csecret, $_POST['twitter-token'], $_POST['twitter-token2']);
224                         $token = $connection->getAccessToken($_POST['twitter-pin']);
225                         //  ok, now that we have the Access Token, save them in the user config
226                         PConfig::set(local_user(), 'twitter', 'oauthtoken', $token['oauth_token']);
227                         PConfig::set(local_user(), 'twitter', 'oauthsecret', $token['oauth_token_secret']);
228                         PConfig::set(local_user(), 'twitter', 'post', 1);
229                         //  reload the Addon Settings page, if we don't do it see Bug #42
230                         goaway('settings/connectors');
231                 } else {
232                         //  if no PIN is supplied in the POST variables, the user has changed the setting
233                         //  to post a tweet for every new __public__ posting to the wall
234                         PConfig::set(local_user(), 'twitter', 'post', intval($_POST['twitter-enable']));
235                         PConfig::set(local_user(), 'twitter', 'post_by_default', intval($_POST['twitter-default']));
236                         PConfig::set(local_user(), 'twitter', 'mirror_posts', intval($_POST['twitter-mirror']));
237                         PConfig::set(local_user(), 'twitter', 'import', intval($_POST['twitter-import']));
238                         PConfig::set(local_user(), 'twitter', 'create_user', intval($_POST['twitter-create_user']));
239
240                         if (!intval($_POST['twitter-mirror'])) {
241                                 PConfig::delete(local_user(), 'twitter', 'lastid');
242                         }
243
244                         info(L10n::t('Twitter settings updated.') . EOL);
245                 }
246         }
247 }
248
249 function twitter_settings(App $a, &$s)
250 {
251         if (!local_user()) {
252                 return;
253         }
254         $a->page['htmlhead'] .= '<link rel="stylesheet"  type="text/css" href="' . $a->get_baseurl() . '/addon/twitter/twitter.css' . '" media="all" />' . "\r\n";
255         /*       * *
256          * 1) Check that we have global consumer key & secret
257          * 2) If no OAuthtoken & stuff is present, generate button to get some
258          * 3) Checkbox for "Send public notices (280 chars only)
259          */
260         $ckey    = Config::get('twitter', 'consumerkey' );
261         $csecret = Config::get('twitter', 'consumersecret' );
262         $otoken  = PConfig::get(local_user(), 'twitter', 'oauthtoken'  );
263         $osecret = PConfig::get(local_user(), 'twitter', 'oauthsecret' );
264
265         $enabled            = intval(PConfig::get(local_user(), 'twitter', 'post'));
266         $defenabled         = intval(PConfig::get(local_user(), 'twitter', 'post_by_default'));
267         $mirrorenabled      = intval(PConfig::get(local_user(), 'twitter', 'mirror_posts'));
268         $importenabled      = intval(PConfig::get(local_user(), 'twitter', 'import'));
269         $create_userenabled = intval(PConfig::get(local_user(), 'twitter', 'create_user'));
270
271         $css = (($enabled) ? '' : '-disabled');
272
273         $s .= '<span id="settings_twitter_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_twitter_expanded\'); openClose(\'settings_twitter_inflated\');">';
274         $s .= '<img class="connector' . $css . '" src="images/twitter.png" /><h3 class="connector">' . L10n::t('Twitter Import/Export/Mirror') . '</h3>';
275         $s .= '</span>';
276         $s .= '<div id="settings_twitter_expanded" class="settings-block" style="display: none;">';
277         $s .= '<span class="fakelink" onclick="openClose(\'settings_twitter_expanded\'); openClose(\'settings_twitter_inflated\');">';
278         $s .= '<img class="connector' . $css . '" src="images/twitter.png" /><h3 class="connector">' . L10n::t('Twitter Import/Export/Mirror') . '</h3>';
279         $s .= '</span>';
280
281         if ((!$ckey) && (!$csecret)) {
282                 /*               * *
283                  * no global consumer keys
284                  * display warning and skip personal config
285                  */
286                 $s .= '<p>' . L10n::t('No consumer key pair for Twitter found. Please contact your site administrator.') . '</p>';
287         } else {
288                 /*               * *
289                  * ok we have a consumer key pair now look into the OAuth stuff
290                  */
291                 if ((!$otoken) && (!$osecret)) {
292                         /*                       * *
293                          * the user has not yet connected the account to twitter...
294                          * get a temporary OAuth key/secret pair and display a button with
295                          * which the user can request a PIN to connect the account to a
296                          * account at Twitter.
297                          */
298                         $connection = new TwitterOAuth($ckey, $csecret);
299                         $request_token = $connection->getRequestToken();
300                         $token = $request_token['oauth_token'];
301                         /*                       * *
302                          *  make some nice form
303                          */
304                         $s .= '<p>' . L10n::t('At this Friendica instance the Twitter addon was enabled but you have not yet connected your account to your Twitter account. To do so click the button below to get a PIN from Twitter which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to Twitter.') . '</p>';
305                         $s .= '<a href="' . $connection->getAuthorizeURL($token) . '" target="_twitter"><img src="addon/twitter/lighter.png" alt="' . L10n::t('Log in with Twitter') . '"></a>';
306                         $s .= '<div id="twitter-pin-wrapper">';
307                         $s .= '<label id="twitter-pin-label" for="twitter-pin">' . L10n::t('Copy the PIN from Twitter here') . '</label>';
308                         $s .= '<input id="twitter-pin" type="text" name="twitter-pin" />';
309                         $s .= '<input id="twitter-token" type="hidden" name="twitter-token" value="' . $token . '" />';
310                         $s .= '<input id="twitter-token2" type="hidden" name="twitter-token2" value="' . $request_token['oauth_token_secret'] . '" />';
311                         $s .= '</div><div class="clear"></div>';
312                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
313                 } else {
314                         /*                       * *
315                          *  we have an OAuth key / secret pair for the user
316                          *  so let's give a chance to disable the postings to Twitter
317                          */
318                         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
319                         $details = $connection->get('account/verify_credentials');
320
321                         $field_checkbox = get_markup_template('field_checkbox.tpl');
322
323                         $s .= '<div id="twitter-info" >
324                                 <p>' . L10n::t('Currently connected to: ') . '<a href="https://twitter.com/' . $details->screen_name . '" target="_twitter">' . $details->screen_name . '</a>
325                                         <button type="submit" name="twitter-disconnect" value="1">' . L10n::t('Disconnect') . '</button>
326                                 </p>
327                                 <p id="twitter-info-block">
328                                         <a href="https://twitter.com/' . $details->screen_name . '" target="_twitter"><img id="twitter-avatar" src="' . $details->profile_image_url . '" /></a>
329                                         <em>' . $details->description . '</em>
330                                 </p>
331                         </div>';
332                         $s .= '<div class="clear"></div>';
333
334                         $s .= replace_macros($field_checkbox, [
335                                 '$field' => ['twitter-enable', L10n::t('Allow posting to Twitter'), $enabled, L10n::t('If enabled all your <strong>public</strong> postings can be posted to the associated Twitter account. You can choose to do so by default (here) or for every posting separately in the posting options when writing the entry.')]
336                         ]);
337                         if ($a->user['hidewall']) {
338                                 $s .= '<p>' . L10n::t('<strong>Note</strong>: Due to your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to Twitter will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted.') . '</p>';
339                         }
340                         $s .= replace_macros($field_checkbox, [
341                                 '$field' => ['twitter-default', L10n::t('Send public postings to Twitter by default'), $defenabled, '']
342                         ]);
343                         $s .= replace_macros($field_checkbox, [
344                                 '$field' => ['twitter-mirror', L10n::t('Mirror all posts from twitter that are no replies'), $mirrorenabled, '']
345                         ]);
346                         $s .= replace_macros($field_checkbox, [
347                                 '$field' => ['twitter-import', L10n::t('Import the remote timeline'), $importenabled, '']
348                         ]);
349                         $s .= replace_macros($field_checkbox, [
350                                 '$field' => ['twitter-create_user', L10n::t('Automatically create contacts'), $create_userenabled, '']
351                         ]);
352
353                         $s .= '<div class="clear"></div>';
354                         $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . L10n::t('Save Settings') . '" /></div>';
355                 }
356         }
357         $s .= '</div><div class="clear"></div>';
358 }
359
360 function twitter_post_local(App $a, &$b)
361 {
362         if ($b['edit']) {
363                 return;
364         }
365
366         if (!local_user() || (local_user() != $b['uid'])) {
367                 return;
368         }
369
370         $twitter_post = intval(PConfig::get(local_user(), 'twitter', 'post'));
371         $twitter_enable = (($twitter_post && x($_REQUEST, 'twitter_enable')) ? intval($_REQUEST['twitter_enable']) : 0);
372
373         // if API is used, default to the chosen settings
374         if ($b['api_source'] && intval(PConfig::get(local_user(), 'twitter', 'post_by_default'))) {
375                 $twitter_enable = 1;
376         }
377
378         if (!$twitter_enable) {
379                 return;
380         }
381
382         if (strlen($b['postopts'])) {
383                 $b['postopts'] .= ',';
384         }
385
386         $b['postopts'] .= 'twitter';
387 }
388
389 function twitter_action(App $a, $uid, $pid, $action)
390 {
391         $ckey = Config::get('twitter', 'consumerkey');
392         $csecret = Config::get('twitter', 'consumersecret');
393         $otoken = PConfig::get($uid, 'twitter', 'oauthtoken');
394         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
395
396         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
397
398         $post = ['id' => $pid];
399
400         logger("twitter_action '" . $action . "' ID: " . $pid . " data: " . print_r($post, true), LOGGER_DATA);
401
402         switch ($action) {
403                 case "delete":
404                         // To-Do: $result = $connection->post('statuses/destroy', $post);
405                         break;
406                 case "like":
407                         $result = $connection->post('favorites/create', $post);
408                         break;
409                 case "unlike":
410                         $result = $connection->post('favorites/destroy', $post);
411                         break;
412         }
413         logger("twitter_action '" . $action . "' send, result: " . print_r($result, true), LOGGER_DEBUG);
414 }
415
416 function twitter_post_hook(App $a, &$b)
417 {
418         // Post to Twitter
419         if (!PConfig::get($b["uid"], 'twitter', 'import')
420                 && ($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))) {
421                 return;
422         }
423
424         if ($b['parent'] != $b['id']) {
425                 logger("twitter_post_hook: parameter " . print_r($b, true), LOGGER_DATA);
426
427                 // Looking if its a reply to a twitter post
428                 if ((substr($b["parent-uri"], 0, 9) != "twitter::") && (substr($b["extid"], 0, 9) != "twitter::") && (substr($b["thr-parent"], 0, 9) != "twitter::")) {
429                         logger("twitter_post_hook: no twitter post " . $b["parent"]);
430                         return;
431                 }
432
433                 $r = q("SELECT * FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
434                         dbesc($b["thr-parent"]),
435                         intval($b["uid"]));
436
437                 if (!count($r)) {
438                         logger("twitter_post_hook: no parent found " . $b["thr-parent"]);
439                         return;
440                 } else {
441                         $iscomment = true;
442                         $orig_post = $r[0];
443                 }
444
445
446                 $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
447                 $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
448                 $nicknameplain = "@" . $nicknameplain;
449
450                 logger("twitter_post_hook: comparing " . $nickname . " and " . $nicknameplain . " with " . $b["body"], LOGGER_DEBUG);
451                 if ((strpos($b["body"], $nickname) === false) && (strpos($b["body"], $nicknameplain) === false)) {
452                         $b["body"] = $nickname . " " . $b["body"];
453                 }
454
455                 logger("twitter_post_hook: parent found " . print_r($orig_post, true), LOGGER_DATA);
456         } else {
457                 $iscomment = false;
458
459                 if ($b['private'] || !strstr($b['postopts'], 'twitter')) {
460                         return;
461                 }
462
463                 // Dont't post if the post doesn't belong to us.
464                 // This is a check for forum postings
465                 $self = dba::selectFirst('contact', ['id'], ['uid' => $b['uid'], 'self' => true]);
466                 if ($b['contact-id'] != $self['id']) {
467                         return;
468                 }
469         }
470
471         if (($b['verb'] == ACTIVITY_POST) && $b['deleted']) {
472                 twitter_action($a, $b["uid"], substr($orig_post["uri"], 9), "delete");
473         }
474
475         if ($b['verb'] == ACTIVITY_LIKE) {
476                 logger("twitter_post_hook: parameter 2 " . substr($b["thr-parent"], 9), LOGGER_DEBUG);
477                 if ($b['deleted'])
478                         twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "unlike");
479                 else
480                         twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "like");
481                 return;
482         }
483
484         if ($b['deleted'] || ($b['created'] !== $b['edited'])) {
485                 return;
486         }
487
488         // if post comes from twitter don't send it back
489         if ($b['extid'] == NETWORK_TWITTER) {
490                 return;
491         }
492
493         if ($b['app'] == "Twitter") {
494                 return;
495         }
496
497         logger('twitter post invoked');
498
499         PConfig::load($b['uid'], 'twitter');
500
501         $ckey    = Config::get('twitter', 'consumerkey');
502         $csecret = Config::get('twitter', 'consumersecret');
503         $otoken  = PConfig::get($b['uid'], 'twitter', 'oauthtoken');
504         $osecret = PConfig::get($b['uid'], 'twitter', 'oauthsecret');
505
506         if ($ckey && $csecret && $otoken && $osecret) {
507                 logger('twitter: we have customer key and oauth stuff, going to send.', LOGGER_DEBUG);
508
509                 // If it's a repeated message from twitter then do a native retweet and exit
510                 if (twitter_is_retweet($a, $b['uid'], $b['body'])) {
511                         return;
512                 }
513
514                 $tweet = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
515
516                 $max_char = 280;
517                 $msgarr = BBCode::toPlaintext($b, $max_char, true, 8);
518                 $msg = $msgarr["text"];
519
520                 if (($msg == "") && isset($msgarr["title"])) {
521                         $msg = Plaintext::shorten($msgarr["title"], $max_char - 50);
522                 }
523
524                 $image = "";
525
526                 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
527                         $msg .= "\n" . $msgarr["url"];
528                 }
529
530                 if (isset($msgarr["image"]) && ($msgarr["type"] != "video")) {
531                         $image = $msgarr["image"];
532                 }
533
534                 // and now tweet it :-)
535                 if (strlen($msg) && ($image != "")) {
536                         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
537                         $media = $connection->upload('media/upload', ['media' => $image]);
538
539                         $post = ['status' => $msg, 'media_ids' => $media->media_id_string];
540
541                         if ($iscomment) {
542                                 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
543                         }
544
545                         $result = $connection->post('statuses/update', $post);
546
547                         logger('twitter_post_with_media send, result: ' . print_r($result, true), LOGGER_DEBUG);
548
549                         if ($result->source) {
550                                 Config::set("twitter", "application_name", strip_tags($result->source));
551                         }
552
553                         if ($result->errors || $result->error) {
554                                 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
555
556                                 // Workaround: Remove the picture link so that the post can be reposted without it
557                                 $msg .= " " . $image;
558                                 $image = "";
559                         } elseif ($iscomment) {
560                                 logger('twitter_post: Update extid ' . $result->id_str . " for post id " . $b['id']);
561                                 q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
562                                         dbesc("twitter::" . $result->id_str),
563                                         dbesc($result->text),
564                                         intval($b['id'])
565                                 );
566                         }
567                 }
568
569                 if (strlen($msg) && ($image == "")) {
570 // -----------------
571                         $max_char = 280;
572                         $msgarr = BBCode::toPlaintext($b, $max_char, true, 8);
573                         $msg = $msgarr["text"];
574
575                         if (($msg == "") && isset($msgarr["title"])) {
576                                 $msg = Plaintext::shorten($msgarr["title"], $max_char - 50);
577                         }
578
579                         if (isset($msgarr["url"])) {
580                                 $msg .= "\n" . $msgarr["url"];
581                         }
582 // -----------------
583                         $url = 'statuses/update';
584                         $post = ['status' => $msg, 'weighted_character_count' => 'true'];
585
586                         if ($iscomment) {
587                                 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
588                         }
589
590                         $result = $tweet->post($url, $post);
591                         logger('twitter_post send, result: ' . print_r($result, true), LOGGER_DEBUG);
592
593                         if ($result->source) {
594                                 Config::set("twitter", "application_name", strip_tags($result->source));
595                         }
596
597                         if ($result->errors) {
598                                 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
599
600                                 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($b['uid']));
601                                 if (count($r)) {
602                                         $a->contact = $r[0]["id"];
603                                 }
604
605                                 $s = serialize(['url' => $url, 'item' => $b['id'], 'post' => $post]);
606
607                                 Queue::add($a->contact, NETWORK_TWITTER, $s);
608                                 notice(L10n::t('Twitter post failed. Queued for retry.') . EOL);
609                         } elseif ($iscomment) {
610                                 logger('twitter_post: Update extid ' . $result->id_str . " for post id " . $b['id']);
611                                 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d",
612                                         dbesc("twitter::" . $result->id_str),
613                                         intval($b['id'])
614                                 );
615                         }
616                 }
617         }
618 }
619
620 function twitter_addon_admin_post(App $a)
621 {
622         $consumerkey     = x($_POST, 'consumerkey')     ? notags(trim($_POST['consumerkey']))     : '';
623         $consumersecret  = x($_POST, 'consumersecret')  ? notags(trim($_POST['consumersecret']))  : '';
624         Config::set('twitter', 'consumerkey', $consumerkey);
625         Config::set('twitter', 'consumersecret', $consumersecret);
626         info(L10n::t('Settings updated.') . EOL);
627 }
628
629 function twitter_addon_admin(App $a, &$o)
630 {
631         $t = get_markup_template("admin.tpl", "addon/twitter/");
632
633         $o = replace_macros($t, [
634                 '$submit' => L10n::t('Save Settings'),
635                 // name, label, value, help, [extra values]
636                 '$consumerkey' => ['consumerkey', L10n::t('Consumer key'), Config::get('twitter', 'consumerkey'), ''],
637                 '$consumersecret' => ['consumersecret', L10n::t('Consumer secret'), Config::get('twitter', 'consumersecret'), ''],
638         ]);
639 }
640
641 function twitter_cron(App $a, $b)
642 {
643         $last = Config::get('twitter', 'last_poll');
644
645         $poll_interval = intval(Config::get('twitter', 'poll_interval'));
646         if (!$poll_interval) {
647                 $poll_interval = TWITTER_DEFAULT_POLL_INTERVAL;
648         }
649
650         if ($last) {
651                 $next = $last + ($poll_interval * 60);
652                 if ($next > time()) {
653                         logger('twitter: poll intervall not reached');
654                         return;
655                 }
656         }
657         logger('twitter: cron_start');
658
659         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'mirror_posts' AND `v` = '1'");
660         if (count($r)) {
661                 foreach ($r as $rr) {
662                         logger('twitter: fetching for user ' . $rr['uid']);
663                         Worker::add(PRIORITY_MEDIUM, "addon/twitter/twitter_sync.php", 1, (int) $rr['uid']);
664                 }
665         }
666
667         $abandon_days = intval(Config::get('system', 'account_abandon_days'));
668         if ($abandon_days < 1)
669                 $abandon_days = 0;
670
671         $abandon_limit = date("Y-m-d H:i:s", time() - $abandon_days * 86400);
672
673         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1'");
674         if (count($r)) {
675                 foreach ($r as $rr) {
676                         if ($abandon_days != 0) {
677                                 $user = q("SELECT `login_date` FROM `user` WHERE uid=%d AND `login_date` >= '%s'", $rr['uid'], $abandon_limit);
678                                 if (!count($user)) {
679                                         logger('abandoned account: timeline from user ' . $rr['uid'] . ' will not be imported');
680                                         continue;
681                                 }
682                         }
683
684                         logger('twitter: importing timeline from user ' . $rr['uid']);
685                         Worker::add(PRIORITY_MEDIUM, "addon/twitter/twitter_sync.php", 2, (int) $rr['uid']);
686                         /*
687                           // To-Do
688                           // check for new contacts once a day
689                           $last_contact_check = PConfig::get($rr['uid'],'pumpio','contact_check');
690                           if($last_contact_check)
691                           $next_contact_check = $last_contact_check + 86400;
692                           else
693                           $next_contact_check = 0;
694
695                           if($next_contact_check <= time()) {
696                           pumpio_getallusers($a, $rr["uid"]);
697                           PConfig::set($rr['uid'],'pumpio','contact_check',time());
698                           }
699                          */
700                 }
701         }
702
703         logger('twitter: cron_end');
704
705         Config::set('twitter', 'last_poll', time());
706 }
707
708 function twitter_expire(App $a, $b)
709 {
710         $days = Config::get('twitter', 'expire');
711
712         if ($days == 0) {
713                 return;
714         }
715
716         if (method_exists('dba', 'delete')) {
717                 $r = dba::select('item', ['id'], ['deleted' => true, 'network' => NETWORK_TWITTER]);
718                 while ($row = dba::fetch($r)) {
719                         dba::delete('item', ['id' => $row['id']]);
720                 }
721                 dba::close($r);
722         } else {
723                 $r = q("DELETE FROM `item` WHERE `deleted` AND `network` = '%s'", dbesc(NETWORK_TWITTER));
724         }
725
726         require_once "include/items.php";
727
728         logger('twitter_expire: expire_start');
729
730         $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
731         if (count($r)) {
732                 foreach ($r as $rr) {
733                         logger('twitter_expire: user ' . $rr['uid']);
734                         item_expire($rr['uid'], $days, NETWORK_TWITTER, true);
735                 }
736         }
737
738         logger('twitter_expire: expire_end');
739 }
740
741 function twitter_prepare_body(App $a, &$b)
742 {
743         if ($b["item"]["network"] != NETWORK_TWITTER) {
744                 return;
745         }
746
747         if ($b["preview"]) {
748                 $max_char = 280;
749                 $item = $b["item"];
750                 $item["plink"] = $a->get_baseurl() . "/display/" . $a->user["nickname"] . "/" . $item["parent"];
751
752                 $r = q("SELECT `author-link` FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
753                         dbesc($item["thr-parent"]),
754                         intval(local_user()));
755
756                 if (count($r)) {
757                         $orig_post = $r[0];
758
759                         $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
760                         $nickname = "@[url=" . $orig_post["author-link"] . "]" . $nicknameplain . "[/url]";
761                         $nicknameplain = "@" . $nicknameplain;
762
763                         if ((strpos($item["body"], $nickname) === false) && (strpos($item["body"], $nicknameplain) === false)) {
764                                 $item["body"] = $nickname . " " . $item["body"];
765                         }
766                 }
767
768                 $msgarr = BBCode::toPlaintext($item, $max_char, true, 8);
769                 $msg = $msgarr["text"];
770
771                 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo")) {
772                         $msg .= " " . $msgarr["url"];
773                 }
774
775                 if (isset($msgarr["image"])) {
776                         $msg .= " " . $msgarr["image"];
777                 }
778
779                 $b['html'] = nl2br(htmlspecialchars($msg));
780         }
781 }
782
783 /**
784  * @brief Build the item array for the mirrored post
785  *
786  * @param App $a Application class
787  * @param integer $uid User id
788  * @param object $post Twitter object with the post
789  *
790  * @return array item data to be posted
791  */
792 function twitter_do_mirrorpost(App $a, $uid, $post)
793 {
794         $datarray["type"] = "wall";
795         $datarray["api_source"] = true;
796         $datarray["profile_uid"] = $uid;
797         $datarray["extid"] = NETWORK_TWITTER;
798         $datarray['message_id'] = item_new_uri($a->get_hostname(), $uid, NETWORK_TWITTER . ":" . $post->id);
799         $datarray['object'] = json_encode($post);
800         $datarray["title"] = "";
801
802         if (is_object($post->retweeted_status)) {
803                 // We don't support nested shares, so we mustn't show quotes as shares on retweets
804                 $item = twitter_createpost($a, $uid, $post->retweeted_status, ['id' => 0], false, false, true);
805
806                 $datarray['body'] = "\n" . share_header($item['author-name'], $item['author-link'], $item['author-avatar'], "", $item['created'], $item['plink']);
807
808                 $datarray['body'] .= $item['body'] . '[/share]';
809         } else {
810                 $item = twitter_createpost($a, $uid, $post, ['id' => 0], false, false, false);
811
812                 $datarray['body'] = $item['body'];
813         }
814
815         $datarray["source"] = $item['app'];
816         $datarray["verb"] = $item['verb'];
817
818         if (isset($item["location"])) {
819                 $datarray["location"] = $item["location"];
820         }
821
822         if (isset($item["coord"])) {
823                 $datarray["coord"] = $item["coord"];
824         }
825
826         return $datarray;
827 }
828
829 function twitter_fetchtimeline(App $a, $uid)
830 {
831         $ckey    = Config::get('twitter', 'consumerkey');
832         $csecret = Config::get('twitter', 'consumersecret');
833         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
834         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
835         $lastid  = PConfig::get($uid, 'twitter', 'lastid');
836
837         $application_name = Config::get('twitter', 'application_name');
838
839         if ($application_name == "") {
840                 $application_name = $a->get_hostname();
841         }
842
843         $has_picture = false;
844
845         require_once 'mod/item.php';
846         require_once 'include/items.php';
847         require_once 'mod/share.php';
848
849         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
850
851         $parameters = ["exclude_replies" => true, "trim_user" => false, "contributor_details" => true, "include_rts" => true, "tweet_mode" => "extended"];
852
853         $first_time = ($lastid == "");
854
855         if ($lastid != "") {
856                 $parameters["since_id"] = $lastid;
857         }
858
859         $items = $connection->get('statuses/user_timeline', $parameters);
860
861         if (!is_array($items)) {
862                 return;
863         }
864
865         $posts = array_reverse($items);
866
867         if (count($posts)) {
868                 foreach ($posts as $post) {
869                         if ($post->id_str > $lastid) {
870                                 $lastid = $post->id_str;
871                                 PConfig::set($uid, 'twitter', 'lastid', $lastid);
872                         }
873
874                         if ($first_time) {
875                                 continue;
876                         }
877
878                         if (!stristr($post->source, $application_name)) {
879                                 $_SESSION["authenticated"] = true;
880                                 $_SESSION["uid"] = $uid;
881
882                                 $_REQUEST = twitter_do_mirrorpost($a, $uid, $post);
883
884                                 logger('twitter: posting for user ' . $uid);
885
886                                 item_post($a);
887                         }
888                 }
889         }
890         PConfig::set($uid, 'twitter', 'lastid', $lastid);
891 }
892
893 function twitter_queue_hook(App $a, &$b)
894 {
895         $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
896                 dbesc(NETWORK_TWITTER)
897         );
898         if (!count($qi)) {
899                 return;
900         }
901
902         foreach ($qi as $x) {
903                 if ($x['network'] !== NETWORK_TWITTER) {
904                         continue;
905                 }
906
907                 logger('twitter_queue: run');
908
909                 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid`
910                         WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
911                         intval($x['cid'])
912                 );
913                 if (!count($r)) {
914                         continue;
915                 }
916
917                 $user = $r[0];
918
919                 $ckey    = Config::get('twitter', 'consumerkey');
920                 $csecret = Config::get('twitter', 'consumersecret');
921                 $otoken  = PConfig::get($user['uid'], 'twitter', 'oauthtoken');
922                 $osecret = PConfig::get($user['uid'], 'twitter', 'oauthsecret');
923
924                 $success = false;
925
926                 if ($ckey && $csecret && $otoken && $osecret) {
927                         logger('twitter_queue: able to post');
928
929                         $z = unserialize($x['content']);
930
931                         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
932                         $result = $connection->post($z['url'], $z['post']);
933
934                         logger('twitter_queue: post result: ' . print_r($result, true), LOGGER_DEBUG);
935
936                         if ($result->errors) {
937                                 logger('twitter_queue: Send to Twitter failed: "' . print_r($result->errors, true) . '"');
938                         } else {
939                                 $success = true;
940                                 Queue::removeItem($x['id']);
941                         }
942                 } else {
943                         logger("twitter_queue: Error getting tokens for user " . $user['uid']);
944                 }
945
946                 if (!$success) {
947                         logger('twitter_queue: delayed');
948                         Queue::updateTime($x['id']);
949                 }
950         }
951 }
952
953 function twitter_fix_avatar($avatar)
954 {
955         $new_avatar = str_replace("_normal.", ".", $avatar);
956
957         $info = Image::getInfoFromURL($new_avatar);
958         if (!$info) {
959                 $new_avatar = $avatar;
960         }
961
962         return $new_avatar;
963 }
964
965 function twitter_fetch_contact($uid, $contact, $create_user)
966 {
967         if ($contact->id_str == "") {
968                 return -1;
969         }
970
971         $avatar = twitter_fix_avatar($contact->profile_image_url_https);
972
973         GContact::update(["url" => "https://twitter.com/" . $contact->screen_name,
974                 "network" => NETWORK_TWITTER, "photo" => $avatar, "hide" => true,
975                 "name" => $contact->name, "nick" => $contact->screen_name,
976                 "location" => $contact->location, "about" => $contact->description,
977                 "addr" => $contact->screen_name . "@twitter.com", "generation" => 2]);
978
979         $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
980                 intval($uid), dbesc("twitter::" . $contact->id_str));
981
982         if (!count($r) && !$create_user) {
983                 return 0;
984         }
985
986         if (count($r) && ($r[0]["readonly"] || $r[0]["blocked"])) {
987                 logger("twitter_fetch_contact: Contact '" . $r[0]["nick"] . "' is blocked or readonly.", LOGGER_DEBUG);
988                 return -1;
989         }
990
991         if (!count($r)) {
992                 // create contact record
993                 q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
994                                         `name`, `nick`, `photo`, `network`, `rel`, `priority`,
995                                         `location`, `about`, `writable`, `blocked`, `readonly`, `pending`)
996                                         VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', %d, 0, 0, 0)",
997                         intval($uid),
998                         dbesc(datetime_convert()),
999                         dbesc("https://twitter.com/" . $contact->screen_name),
1000                         dbesc(normalise_link("https://twitter.com/" . $contact->screen_name)),
1001                         dbesc($contact->screen_name."@twitter.com"),
1002                         dbesc("twitter::" . $contact->id_str),
1003                         dbesc(''),
1004                         dbesc("twitter::" . $contact->id_str),
1005                         dbesc($contact->name),
1006                         dbesc($contact->screen_name),
1007                         dbesc($avatar),
1008                         dbesc(NETWORK_TWITTER),
1009                         intval(CONTACT_IS_FRIEND),
1010                         intval(1),
1011                         dbesc($contact->location),
1012                         dbesc($contact->description),
1013                         intval(1)
1014                 );
1015
1016                 $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d LIMIT 1",
1017                         dbesc("twitter::".$contact->id_str),
1018                         intval($uid)
1019                 );
1020
1021                 if (!count($r)) {
1022                         return false;
1023                 }
1024
1025                 $contact_id = $r[0]['id'];
1026
1027                 Group::addMember(User::getDefaultGroup($uid), $contact_id);
1028
1029                 $photos = Photo::importProfilePhoto($avatar, $uid, $contact_id, true);
1030
1031                 if ($photos) {
1032                         q("UPDATE `contact` SET `photo` = '%s',
1033                                                 `thumb` = '%s',
1034                                                 `micro` = '%s',
1035                                                 `name-date` = '%s',
1036                                                 `uri-date` = '%s',
1037                                                         `avatar-date` = '%s'
1038                                         WHERE `id` = %d",
1039                                 dbesc($photos[0]),
1040                                 dbesc($photos[1]),
1041                                 dbesc($photos[2]),
1042                                 dbesc(datetime_convert()),
1043                                 dbesc(datetime_convert()),
1044                                 dbesc(datetime_convert()),
1045                                 intval($contact_id)
1046                         );
1047                 }
1048         } else {
1049                 // update profile photos once every two weeks as we have no notification of when they change.
1050                 //$update_photo = (($r[0]['avatar-date'] < datetime_convert('','','now -2 days')) ? true : false);
1051                 $update_photo = ($r[0]['avatar-date'] < datetime_convert('', '', 'now -12 hours'));
1052
1053                 // check that we have all the photos, this has been known to fail on occasion
1054                 if ((!$r[0]['photo']) || (!$r[0]['thumb']) || (!$r[0]['micro']) || ($update_photo)) {
1055                         logger("twitter_fetch_contact: Updating contact " . $contact->screen_name, LOGGER_DEBUG);
1056
1057                         $photos = Photo::importProfilePhoto($avatar, $uid, $r[0]['id'], true);
1058
1059                         if ($photos) {
1060                                 q("UPDATE `contact` SET `photo` = '%s',
1061                                                         `thumb` = '%s',
1062                                                         `micro` = '%s',
1063                                                         `name-date` = '%s',
1064                                                         `uri-date` = '%s',
1065                                                         `avatar-date` = '%s',
1066                                                         `url` = '%s',
1067                                                         `nurl` = '%s',
1068                                                         `addr` = '%s',
1069                                                         `name` = '%s',
1070                                                         `nick` = '%s',
1071                                                         `location` = '%s',
1072                                                         `about` = '%s'
1073                                                 WHERE `id` = %d",
1074                                         dbesc($photos[0]),
1075                                         dbesc($photos[1]),
1076                                         dbesc($photos[2]),
1077                                         dbesc(datetime_convert()),
1078                                         dbesc(datetime_convert()),
1079                                         dbesc(datetime_convert()),
1080                                         dbesc("https://twitter.com/".$contact->screen_name),
1081                                         dbesc(normalise_link("https://twitter.com/".$contact->screen_name)),
1082                                         dbesc($contact->screen_name."@twitter.com"),
1083                                         dbesc($contact->name),
1084                                         dbesc($contact->screen_name),
1085                                         dbesc($contact->location),
1086                                         dbesc($contact->description),
1087                                         intval($r[0]['id'])
1088                                 );
1089                         }
1090                 }
1091         }
1092
1093         return $r[0]["id"];
1094 }
1095
1096 function twitter_fetchuser(App $a, $uid, $screen_name = "", $user_id = "")
1097 {
1098         $ckey    = Config::get('twitter', 'consumerkey');
1099         $csecret = Config::get('twitter', 'consumersecret');
1100         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
1101         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
1102
1103         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1104                 intval($uid));
1105
1106         if (count($r)) {
1107                 $self = $r[0];
1108         } else {
1109                 return;
1110         }
1111
1112         $parameters = [];
1113
1114         if ($screen_name != "") {
1115                 $parameters["screen_name"] = $screen_name;
1116         }
1117
1118         if ($user_id != "") {
1119                 $parameters["user_id"] = $user_id;
1120         }
1121
1122         // Fetching user data
1123         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
1124         $user = $connection->get('users/show', $parameters);
1125
1126         if (!is_object($user)) {
1127                 return;
1128         }
1129
1130         $contact_id = twitter_fetch_contact($uid, $user, true);
1131
1132         return $contact_id;
1133 }
1134
1135 function twitter_expand_entities(App $a, $body, $item, $no_tags = false, $picture)
1136 {
1137         $tags = "";
1138
1139         $plain = $body;
1140
1141         if (isset($item->entities->urls)) {
1142                 $type = "";
1143                 $footerurl = "";
1144                 $footerlink = "";
1145                 $footer = "";
1146
1147                 foreach ($item->entities->urls as $url) {
1148                         $plain = str_replace($url->url, '', $plain);
1149
1150                         if ($url->url && $url->expanded_url && $url->display_url) {
1151                                 $expanded_url = Network::finalUrl($url->expanded_url);
1152
1153                                 $oembed_data = OEmbed::fetchURL($expanded_url);
1154
1155                                 // Quickfix: Workaround for URL with "[" and "]" in it
1156                                 if (strpos($expanded_url, "[") || strpos($expanded_url, "]")) {
1157                                         $expanded_url = $url->url;
1158                                 }
1159
1160                                 if ($type == "") {
1161                                         $type = $oembed_data->type;
1162                                 }
1163
1164                                 if ($oembed_data->type == "video") {
1165                                         //$body = str_replace($url->url,
1166                                         //              "[video]".$expanded_url."[/video]", $body);
1167                                         //$dontincludemedia = true;
1168                                         $type = $oembed_data->type;
1169                                         $footerurl = $expanded_url;
1170                                         $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1171
1172                                         $body = str_replace($url->url, $footerlink, $body);
1173                                         //} elseif (($oembed_data->type == "photo") AND isset($oembed_data->url) AND !$dontincludemedia) {
1174                                 } elseif (($oembed_data->type == "photo") && isset($oembed_data->url)) {
1175                                         $body = str_replace($url->url, "[url=" . $expanded_url . "][img]" . $oembed_data->url . "[/img][/url]", $body);
1176                                         //$dontincludemedia = true;
1177                                 } elseif ($oembed_data->type != "link") {
1178                                         $body = str_replace($url->url, "[url=" . $expanded_url . "]" . $expanded_url . "[/url]", $body);
1179                                 } else {
1180                                         $img_str = Network::fetchUrl($expanded_url, true, $redirects, 4);
1181
1182                                         $tempfile = tempnam(get_temppath(), "cache");
1183                                         file_put_contents($tempfile, $img_str);
1184                                         $mime = image_type_to_mime_type(exif_imagetype($tempfile));
1185                                         unlink($tempfile);
1186
1187                                         if (substr($mime, 0, 6) == "image/") {
1188                                                 $type = "photo";
1189                                                 $body = str_replace($url->url, "[img]" . $expanded_url . "[/img]", $body);
1190                                                 //$dontincludemedia = true;
1191                                         } else {
1192                                                 $type = $oembed_data->type;
1193                                                 $footerurl = $expanded_url;
1194                                                 $footerlink = "[url=" . $expanded_url . "]" . $expanded_url . "[/url]";
1195
1196                                                 $body = str_replace($url->url, $footerlink, $body);
1197                                         }
1198                                 }
1199                         }
1200                 }
1201
1202                 if ($footerurl != "") {
1203                         $footer = add_page_info($footerurl, false, $picture);
1204                 }
1205
1206                 if (($footerlink != "") && (trim($footer) != "")) {
1207                         $removedlink = trim(str_replace($footerlink, "", $body));
1208
1209                         if (($removedlink == "") || strstr($body, $removedlink)) {
1210                                 $body = $removedlink;
1211                         }
1212
1213                         $body .= $footer;
1214                 }
1215
1216                 if (($footer == "") && ($picture != "")) {
1217                         $body .= "\n\n[img]" . $picture . "[/img]\n";
1218                 } elseif (($footer == "") && ($picture == "")) {
1219                         $body = add_page_info_to_body($body);
1220                 }
1221
1222                 if ($no_tags) {
1223                         return ["body" => $body, "tags" => "", "plain" => $plain];
1224                 }
1225
1226                 $tags_arr = [];
1227
1228                 foreach ($item->entities->hashtags AS $hashtag) {
1229                         $url = "#[url=" . $a->get_baseurl() . "/search?tag=" . rawurlencode($hashtag->text) . "]" . $hashtag->text . "[/url]";
1230                         $tags_arr["#" . $hashtag->text] = $url;
1231                         $body = str_replace("#" . $hashtag->text, $url, $body);
1232                 }
1233
1234                 foreach ($item->entities->user_mentions AS $mention) {
1235                         $url = "@[url=https://twitter.com/" . rawurlencode($mention->screen_name) . "]" . $mention->screen_name . "[/url]";
1236                         $tags_arr["@" . $mention->screen_name] = $url;
1237                         $body = str_replace("@" . $mention->screen_name, $url, $body);
1238                 }
1239
1240                 // it seems as if the entities aren't always covering all mentions. So the rest will be checked here
1241                 $tags = get_tags($body);
1242
1243                 if (count($tags)) {
1244                         foreach ($tags as $tag) {
1245                                 if (strstr(trim($tag), " ")) {
1246                                         continue;
1247                                 }
1248
1249                                 if (strpos($tag, '#') === 0) {
1250                                         if (strpos($tag, '[url=')) {
1251                                                 continue;
1252                                         }
1253
1254                                         // don't link tags that are already embedded in links
1255                                         if (preg_match('/\[(.*?)' . preg_quote($tag, '/') . '(.*?)\]/', $body)) {
1256                                                 continue;
1257                                         }
1258                                         if (preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag, '/') . '(.*?)\)/', $body)) {
1259                                                 continue;
1260                                         }
1261
1262                                         $basetag = str_replace('_', ' ', substr($tag, 1));
1263                                         $url = '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
1264                                         $body = str_replace($tag, $url, $body);
1265                                         $tags_arr["#" . $basetag] = $url;
1266                                 } elseif (strpos($tag, '@') === 0) {
1267                                         if (strpos($tag, '[url=')) {
1268                                                 continue;
1269                                         }
1270
1271                                         $basetag = substr($tag, 1);
1272                                         $url = '@[url=https://twitter.com/' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
1273                                         $body = str_replace($tag, $url, $body);
1274                                         $tags_arr["@" . $basetag] = $url;
1275                                 }
1276                         }
1277                 }
1278
1279                 $tags = implode($tags_arr, ",");
1280         }
1281         return ["body" => $body, "tags" => $tags, "plain" => $plain];
1282 }
1283
1284 /**
1285  * @brief Fetch media entities and add media links to the body
1286  *
1287  * @param object $post Twitter object with the post
1288  * @param array $postarray Array of the item that is about to be posted
1289  *
1290  * @return $picture string Returns a a single picture string if it isn't a media post
1291  */
1292 function twitter_media_entities($post, &$postarray)
1293 {
1294         // There are no media entities? So we quit.
1295         if (!is_array($post->extended_entities->media)) {
1296                 return "";
1297         }
1298
1299         // When the post links to an external page, we only take one picture.
1300         // We only do this when there is exactly one media.
1301         if ((count($post->entities->urls) > 0) && (count($post->extended_entities->media) == 1)) {
1302                 $picture = "";
1303                 foreach ($post->extended_entities->media AS $medium) {
1304                         if (isset($medium->media_url_https)) {
1305                                 $picture = $medium->media_url_https;
1306                                 $postarray['body'] = str_replace($medium->url, "", $postarray['body']);
1307                         }
1308                 }
1309                 return $picture;
1310         }
1311
1312         // This is a pure media post, first search for all media urls
1313         $media = [];
1314         foreach ($post->extended_entities->media AS $medium) {
1315                 switch ($medium->type) {
1316                         case 'photo':
1317                                 $media[$medium->url] .= "\n[img]" . $medium->media_url_https . "[/img]";
1318                                 $postarray['object-type'] = ACTIVITY_OBJ_IMAGE;
1319                                 break;
1320                         case 'video':
1321                         case 'animated_gif':
1322                                 $media[$medium->url] .= "\n[img]" . $medium->media_url_https . "[/img]";
1323                                 $postarray['object-type'] = ACTIVITY_OBJ_VIDEO;
1324                                 if (is_array($medium->video_info->variants)) {
1325                                         $bitrate = 0;
1326                                         // We take the video with the highest bitrate
1327                                         foreach ($medium->video_info->variants AS $variant) {
1328                                                 if (($variant->content_type == "video/mp4") && ($variant->bitrate >= $bitrate)) {
1329                                                         $media[$medium->url] = "\n[video]" . $variant->url . "[/video]";
1330                                                         $bitrate = $variant->bitrate;
1331                                                 }
1332                                         }
1333                                 }
1334                                 break;
1335                         // The following code will only be activated for test reasons
1336                         //default:
1337                         //      $postarray['body'] .= print_r($medium, true);
1338                 }
1339         }
1340
1341         // Now we replace the media urls.
1342         foreach ($media AS $key => $value) {
1343                 $postarray['body'] = str_replace($key, "\n" . $value . "\n", $postarray['body']);
1344         }
1345         return "";
1346 }
1347
1348 function twitter_createpost(App $a, $uid, $post, $self, $create_user, $only_existing_contact, $noquote)
1349 {
1350         $postarray = [];
1351         $postarray['network'] = NETWORK_TWITTER;
1352         $postarray['gravity'] = 0;
1353         $postarray['uid'] = $uid;
1354         $postarray['wall'] = 0;
1355         $postarray['uri'] = "twitter::" . $post->id_str;
1356         $postarray['object'] = json_encode($post);
1357
1358         // Don't import our own comments
1359         $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1360                         dbesc($postarray['uri']),
1361                         intval($uid)
1362         );
1363
1364         if (count($r)) {
1365                 logger("Item with extid " . $postarray['uri'] . " found.", LOGGER_DEBUG);
1366                 return [];
1367         }
1368
1369         $contactid = 0;
1370
1371         if ($post->in_reply_to_status_id_str != "") {
1372                 $parent = "twitter::" . $post->in_reply_to_status_id_str;
1373
1374                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1375                                 dbesc($parent),
1376                                 intval($uid)
1377                 );
1378                 if (count($r)) {
1379                         $postarray['thr-parent'] = $r[0]["uri"];
1380                         $postarray['parent-uri'] = $r[0]["parent-uri"];
1381                         $postarray['parent'] = $r[0]["parent"];
1382                         $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1383                 } else {
1384                         $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1385                                         dbesc($parent),
1386                                         intval($uid)
1387                         );
1388                         if (count($r)) {
1389                                 $postarray['thr-parent'] = $r[0]['uri'];
1390                                 $postarray['parent-uri'] = $r[0]['parent-uri'];
1391                                 $postarray['parent'] = $r[0]['parent'];
1392                                 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1393                         } else {
1394                                 $postarray['thr-parent'] = $postarray['uri'];
1395                                 $postarray['parent-uri'] = $postarray['uri'];
1396                                 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1397                         }
1398                 }
1399
1400                 // Is it me?
1401                 $own_id = PConfig::get($uid, 'twitter', 'own_id');
1402
1403                 if ($post->user->id_str == $own_id) {
1404                         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1405                                 intval($uid));
1406
1407                         if (count($r)) {
1408                                 $contactid = $r[0]["id"];
1409
1410                                 $postarray['owner-name']   = $r[0]["name"];
1411                                 $postarray['owner-link']   = $r[0]["url"];
1412                                 $postarray['owner-avatar'] = $r[0]["photo"];
1413                         } else {
1414                                 logger("No self contact for user " . $uid, LOGGER_DEBUG);
1415                                 return [];
1416                         }
1417                 }
1418                 // Don't create accounts of people who just comment something
1419                 $create_user = false;
1420         } else {
1421                 $postarray['parent-uri'] = $postarray['uri'];
1422                 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1423         }
1424
1425         if ($contactid == 0) {
1426                 $contactid = twitter_fetch_contact($uid, $post->user, $create_user);
1427
1428                 $postarray['owner-name'] = $post->user->name;
1429                 $postarray['owner-link'] = "https://twitter.com/" . $post->user->screen_name;
1430                 $postarray['owner-avatar'] = twitter_fix_avatar($post->user->profile_image_url_https);
1431         }
1432
1433         if (($contactid == 0) && !$only_existing_contact) {
1434                 $contactid = $self['id'];
1435         } elseif ($contactid <= 0) {
1436                 logger("Contact ID is zero or less than zero.", LOGGER_DEBUG);
1437                 return [];
1438         }
1439
1440         $postarray['contact-id'] = $contactid;
1441
1442         $postarray['verb'] = ACTIVITY_POST;
1443         $postarray['author-name'] = $postarray['owner-name'];
1444         $postarray['author-link'] = $postarray['owner-link'];
1445         $postarray['author-avatar'] = $postarray['owner-avatar'];
1446         $postarray['plink'] = "https://twitter.com/" . $post->user->screen_name . "/status/" . $post->id_str;
1447         $postarray['app'] = strip_tags($post->source);
1448
1449         if ($post->user->protected) {
1450                 $postarray['private'] = 1;
1451                 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1452         }
1453
1454         if (is_string($post->full_text)) {
1455                 $postarray['body'] = $post->full_text;
1456         } else {
1457                 $postarray['body'] = $post->text;
1458         }
1459
1460         // When the post contains links then use the correct object type
1461         if (count($post->entities->urls) > 0) {
1462                 $postarray['object-type'] = ACTIVITY_OBJ_BOOKMARK;
1463         }
1464
1465         // Search for media links
1466         $picture = twitter_media_entities($post, $postarray);
1467
1468         $converted = twitter_expand_entities($a, $postarray['body'], $post, false, $picture);
1469         $postarray['body'] = $converted["body"];
1470         $postarray['tag'] = $converted["tags"];
1471         $postarray['created'] = datetime_convert('UTC', 'UTC', $post->created_at);
1472         $postarray['edited'] = datetime_convert('UTC', 'UTC', $post->created_at);
1473
1474         $statustext = $converted["plain"];
1475
1476         if (is_string($post->place->name)) {
1477                 $postarray["location"] = $post->place->name;
1478         }
1479         if (is_string($post->place->full_name)) {
1480                 $postarray["location"] = $post->place->full_name;
1481         }
1482         if (is_array($post->geo->coordinates)) {
1483                 $postarray["coord"] = $post->geo->coordinates[0] . " " . $post->geo->coordinates[1];
1484         }
1485         if (is_array($post->coordinates->coordinates)) {
1486                 $postarray["coord"] = $post->coordinates->coordinates[1] . " " . $post->coordinates->coordinates[0];
1487         }
1488         if (is_object($post->retweeted_status)) {
1489                 $retweet = twitter_createpost($a, $uid, $post->retweeted_status, $self, false, false, $noquote);
1490
1491                 $retweet['object'] = $postarray['object'];
1492                 $retweet['private'] = $postarray['private'];
1493                 $retweet['allow_cid'] = $postarray['allow_cid'];
1494                 $retweet['contact-id'] = $postarray['contact-id'];
1495                 $retweet['owner-name'] = $postarray['owner-name'];
1496                 $retweet['owner-link'] = $postarray['owner-link'];
1497                 $retweet['owner-avatar'] = $postarray['owner-avatar'];
1498
1499                 $postarray = $retweet;
1500         }
1501
1502         if (is_object($post->quoted_status) && !$noquote) {
1503                 $quoted = twitter_createpost($a, $uid, $post->quoted_status, $self, false, false, true);
1504
1505                 $postarray['body'] = $statustext;
1506
1507                 $postarray['body'] .= "\n" . share_header($quoted['author-name'], $quoted['author-link'], $quoted['author-avatar'], "", $quoted['created'], $quoted['plink']);
1508
1509                 $postarray['body'] .= $quoted['body'] . '[/share]';
1510         }
1511
1512         return $postarray;
1513 }
1514
1515 function twitter_checknotification(App $a, $uid, $own_id, $top_item, $postarray)
1516 {
1517         /// TODO: this whole function doesn't seem to work. Needs complete check
1518         $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
1519                         intval($uid)
1520         );
1521
1522         if (!count($user)) {
1523                 return;
1524         }
1525
1526         // Is it me?
1527         if (link_compare($user[0]["url"], $postarray['author-link'])) {
1528                 return;
1529         }
1530
1531         $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1532                         intval($uid),
1533                         dbesc("twitter::".$own_id)
1534         );
1535
1536         if (!count($own_user)) {
1537                 return;
1538         }
1539
1540         // Is it me from twitter?
1541         if (link_compare($own_user[0]["url"], $postarray['author-link'])) {
1542                 return;
1543         }
1544
1545         $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
1546                         dbesc($postarray['parent-uri']),
1547                         intval($uid)
1548         );
1549
1550         if (count($myconv)) {
1551                 foreach ($myconv as $conv) {
1552                         // now if we find a match, it means we're in this conversation
1553                         if (!link_compare($conv['author-link'], $user[0]["url"]) && !link_compare($conv['author-link'], $own_user[0]["url"])) {
1554                                 continue;
1555                         }
1556
1557                         require_once 'include/enotify.php';
1558
1559                         $conv_parent = $conv['parent'];
1560
1561                         notification([
1562                                 'type' => NOTIFY_COMMENT,
1563                                 'notify_flags' => $user[0]['notify-flags'],
1564                                 'language' => $user[0]['language'],
1565                                 'to_name' => $user[0]['username'],
1566                                 'to_email' => $user[0]['email'],
1567                                 'uid' => $user[0]['uid'],
1568                                 'item' => $postarray,
1569                                 'link' => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($top_item)),
1570                                 'source_name' => $postarray['author-name'],
1571                                 'source_link' => $postarray['author-link'],
1572                                 'source_photo' => $postarray['author-avatar'],
1573                                 'verb' => ACTIVITY_POST,
1574                                 'otype' => 'item',
1575                                 'parent' => $conv_parent,
1576                         ]);
1577
1578                         // only send one notification
1579                         break;
1580                 }
1581         }
1582 }
1583
1584 function twitter_fetchparentposts(App $a, $uid, $post, $connection, $self, $own_id)
1585 {
1586         logger("twitter_fetchparentposts: Fetching for user " . $uid . " and post " . $post->id_str, LOGGER_DEBUG);
1587
1588         $posts = [];
1589
1590         while ($post->in_reply_to_status_id_str != "") {
1591                 $parameters = ["trim_user" => false, "tweet_mode" => "extended", "id" => $post->in_reply_to_status_id_str];
1592
1593                 $post = $connection->get('statuses/show', $parameters);
1594
1595                 if (!count($post)) {
1596                         logger("twitter_fetchparentposts: Can't fetch post " . $parameters->id, LOGGER_DEBUG);
1597                         break;
1598                 }
1599
1600                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1601                                 dbesc("twitter::".$post->id_str),
1602                                 intval($uid)
1603                 );
1604
1605                 if (count($r)) {
1606                         break;
1607                 }
1608
1609                 $posts[] = $post;
1610         }
1611
1612         logger("twitter_fetchparentposts: Fetching " . count($posts) . " parents", LOGGER_DEBUG);
1613
1614         $posts = array_reverse($posts);
1615
1616         if (count($posts)) {
1617                 foreach ($posts as $post) {
1618                         $postarray = twitter_createpost($a, $uid, $post, $self, false, false, false);
1619
1620                         if (trim($postarray['body']) == "")
1621                                 continue;
1622
1623                         $item = item_store($postarray);
1624                         $postarray["id"] = $item;
1625
1626                         logger('twitter_fetchparentpost: User ' . $self["nick"] . ' posted parent timeline item ' . $item);
1627
1628                         if ($item && !function_exists("check_item_notification")) {
1629                                 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
1630                         }
1631                 }
1632         }
1633 }
1634
1635 function twitter_fetchhometimeline(App $a, $uid)
1636 {
1637         $ckey    = Config::get('twitter', 'consumerkey');
1638         $csecret = Config::get('twitter', 'consumersecret');
1639         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
1640         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
1641         $create_user = PConfig::get($uid, 'twitter', 'create_user');
1642         $mirror_posts = PConfig::get($uid, 'twitter', 'mirror_posts');
1643
1644         logger("twitter_fetchhometimeline: Fetching for user " . $uid, LOGGER_DEBUG);
1645
1646         $application_name = Config::get('twitter', 'application_name');
1647
1648         if ($application_name == "") {
1649                 $application_name = $a->get_hostname();
1650         }
1651
1652         require_once 'include/items.php';
1653
1654         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
1655
1656         $own_contact = twitter_fetch_own_contact($a, $uid);
1657
1658         $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1659                 intval($own_contact),
1660                 intval($uid));
1661
1662         if (count($r)) {
1663                 $own_id = $r[0]["nick"];
1664         } else {
1665                 logger("twitter_fetchhometimeline: Own twitter contact not found for user " . $uid, LOGGER_DEBUG);
1666                 return;
1667         }
1668
1669         $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1670                 intval($uid));
1671
1672         if (count($r)) {
1673                 $self = $r[0];
1674         } else {
1675                 logger("twitter_fetchhometimeline: Own contact not found for user " . $uid, LOGGER_DEBUG);
1676                 return;
1677         }
1678
1679         $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1680                 intval($uid));
1681         if (!count($u)) {
1682                 logger("twitter_fetchhometimeline: Own user not found for user " . $uid, LOGGER_DEBUG);
1683                 return;
1684         }
1685
1686         $parameters = ["exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true, "tweet_mode" => "extended"];
1687         //$parameters["count"] = 200;
1688         // Fetching timeline
1689         $lastid = PConfig::get($uid, 'twitter', 'lasthometimelineid');
1690
1691         $first_time = ($lastid == "");
1692
1693         if ($lastid != "") {
1694                 $parameters["since_id"] = $lastid;
1695         }
1696
1697         $items = $connection->get('statuses/home_timeline', $parameters);
1698
1699         if (!is_array($items)) {
1700                 logger("twitter_fetchhometimeline: Error fetching home timeline: " . print_r($items, true), LOGGER_DEBUG);
1701                 return;
1702         }
1703
1704         $posts = array_reverse($items);
1705
1706         logger("twitter_fetchhometimeline: Fetching timeline for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1707
1708         if (count($posts)) {
1709                 foreach ($posts as $post) {
1710                         if ($post->id_str > $lastid) {
1711                                 $lastid = $post->id_str;
1712                                 PConfig::set($uid, 'twitter', 'lasthometimelineid', $lastid);
1713                         }
1714
1715                         if ($first_time) {
1716                                 continue;
1717                         }
1718
1719                         if (stristr($post->source, $application_name) && $post->user->screen_name == $own_id) {
1720                                 logger("twitter_fetchhometimeline: Skip previously sended post", LOGGER_DEBUG);
1721                                 continue;
1722                         }
1723
1724                         if ($mirror_posts && $post->user->screen_name == $own_id && $post->in_reply_to_status_id_str == "") {
1725                                 logger("twitter_fetchhometimeline: Skip post that will be mirrored", LOGGER_DEBUG);
1726                                 continue;
1727                         }
1728
1729                         if ($post->in_reply_to_status_id_str != "") {
1730                                 twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
1731                         }
1732
1733                         $postarray = twitter_createpost($a, $uid, $post, $self, $create_user, true, false);
1734
1735                         if (trim($postarray['body']) == "") {
1736                                 continue;
1737                         }
1738
1739                         $item = item_store($postarray);
1740                         $postarray["id"] = $item;
1741
1742                         logger('twitter_fetchhometimeline: User ' . $self["nick"] . ' posted home timeline item ' . $item);
1743
1744                         if ($item && !function_exists("check_item_notification")) {
1745                                 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
1746                         }
1747                 }
1748         }
1749         PConfig::set($uid, 'twitter', 'lasthometimelineid', $lastid);
1750
1751         // Fetching mentions
1752         $lastid = PConfig::get($uid, 'twitter', 'lastmentionid');
1753
1754         $first_time = ($lastid == "");
1755
1756         if ($lastid != "") {
1757                 $parameters["since_id"] = $lastid;
1758         }
1759
1760         $items = $connection->get('statuses/mentions_timeline', $parameters);
1761
1762         if (!is_array($items)) {
1763                 logger("twitter_fetchhometimeline: Error fetching mentions: " . print_r($items, true), LOGGER_DEBUG);
1764                 return;
1765         }
1766
1767         $posts = array_reverse($items);
1768
1769         logger("twitter_fetchhometimeline: Fetching mentions for user " . $uid . " " . sizeof($posts) . " items", LOGGER_DEBUG);
1770
1771         if (count($posts)) {
1772                 foreach ($posts as $post) {
1773                         if ($post->id_str > $lastid) {
1774                                 $lastid = $post->id_str;
1775                         }
1776
1777                         if ($first_time) {
1778                                 continue;
1779                         }
1780
1781                         if ($post->in_reply_to_status_id_str != "") {
1782                                 twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
1783                         }
1784
1785                         $postarray = twitter_createpost($a, $uid, $post, $self, false, false, false);
1786
1787                         if (trim($postarray['body']) == "") {
1788                                 continue;
1789                         }
1790
1791                         $item = item_store($postarray);
1792                         $postarray["id"] = $item;
1793
1794                         if ($item && function_exists("check_item_notification")) {
1795                                 check_item_notification($item, $uid, NOTIFY_TAGSELF);
1796                         }
1797
1798                         if (!isset($postarray["parent"]) || ($postarray["parent"] == 0)) {
1799                                 $postarray["parent"] = $item;
1800                         }
1801
1802                         logger('twitter_fetchhometimeline: User ' . $self["nick"] . ' posted mention timeline item ' . $item);
1803
1804                         if ($item == 0) {
1805                                 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1806                                         dbesc($postarray['uri']),
1807                                         intval($uid)
1808                                 );
1809                                 if (count($r)) {
1810                                         $item = $r[0]['id'];
1811                                         $parent_id = $r[0]['parent'];
1812                                 }
1813                         } else
1814                                 $parent_id = $postarray['parent'];
1815
1816                         if (($item != 0) && !function_exists("check_item_notification")) {
1817                                 require_once 'include/enotify.php';
1818                                 notification([
1819                                         'type'         => NOTIFY_TAGSELF,
1820                                         'notify_flags' => $u[0]['notify-flags'],
1821                                         'language'     => $u[0]['language'],
1822                                         'to_name'      => $u[0]['username'],
1823                                         'to_email'     => $u[0]['email'],
1824                                         'uid'          => $u[0]['uid'],
1825                                         'item'         => $postarray,
1826                                         'link'         => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($item)),
1827                                         'source_name'  => $postarray['author-name'],
1828                                         'source_link'  => $postarray['author-link'],
1829                                         'source_photo' => $postarray['author-avatar'],
1830                                         'verb'         => ACTIVITY_TAG,
1831                                         'otype'        => 'item',
1832                                         'parent'       => $parent_id
1833                                 ]);
1834                         }
1835                 }
1836         }
1837
1838         PConfig::set($uid, 'twitter', 'lastmentionid', $lastid);
1839 }
1840
1841 function twitter_fetch_own_contact(App $a, $uid)
1842 {
1843         $ckey    = Config::get('twitter', 'consumerkey');
1844         $csecret = Config::get('twitter', 'consumersecret');
1845         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
1846         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
1847
1848         $own_id = PConfig::get($uid, 'twitter', 'own_id');
1849
1850         $contact_id = 0;
1851
1852         if ($own_id == "") {
1853                 $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
1854
1855                 // Fetching user data
1856                 $user = $connection->get('account/verify_credentials');
1857
1858                 PConfig::set($uid, 'twitter', 'own_id', $user->id_str);
1859
1860                 $contact_id = twitter_fetch_contact($uid, $user, true);
1861         } else {
1862                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1863                         intval($uid),
1864                         dbesc("twitter::" . $own_id));
1865                 if (count($r)) {
1866                         $contact_id = $r[0]["id"];
1867                 } else {
1868                         PConfig::delete($uid, 'twitter', 'own_id');
1869                 }
1870         }
1871
1872         return $contact_id;
1873 }
1874
1875 function twitter_is_retweet(App $a, $uid, $body)
1876 {
1877         $body = trim($body);
1878
1879         // Skip if it isn't a pure repeated messages
1880         // Does it start with a share?
1881         if (strpos($body, "[share") > 0) {
1882                 return false;
1883         }
1884
1885         // Does it end with a share?
1886         if (strlen($body) > (strrpos($body, "[/share]") + 8)) {
1887                 return false;
1888         }
1889
1890         $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$1", $body);
1891         // Skip if there is no shared message in there
1892         if ($body == $attributes) {
1893                 return false;
1894         }
1895
1896         $link = "";
1897         preg_match("/link='(.*?)'/ism", $attributes, $matches);
1898         if ($matches[1] != "") {
1899                 $link = $matches[1];
1900         }
1901
1902         preg_match('/link="(.*?)"/ism', $attributes, $matches);
1903         if ($matches[1] != "") {
1904                 $link = $matches[1];
1905         }
1906
1907         $id = preg_replace("=https?://twitter.com/(.*)/status/(.*)=ism", "$2", $link);
1908         if ($id == $link) {
1909                 return false;
1910         }
1911
1912         logger('twitter_is_retweet: Retweeting id ' . $id . ' for user ' . $uid, LOGGER_DEBUG);
1913
1914         $ckey    = Config::get('twitter', 'consumerkey');
1915         $csecret = Config::get('twitter', 'consumersecret');
1916         $otoken  = PConfig::get($uid, 'twitter', 'oauthtoken');
1917         $osecret = PConfig::get($uid, 'twitter', 'oauthsecret');
1918
1919         $connection = new TwitterOAuth($ckey, $csecret, $otoken, $osecret);
1920
1921         $result = $connection->post('statuses/retweet/' . $id);
1922
1923         logger('twitter_is_retweet: result ' . print_r($result, true), LOGGER_DEBUG);
1924
1925         return !isset($result->errors);
1926 }