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