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