3 * Name: Twitter Connector
4 * Description: Bidirectional (posting, relaying and reading) connector for Twitter.
6 * Author: Tobias Diekershoff <https://f.diekershoff.de/profile/tobias>
7 * Author: Michael Vogel <https://pirati.ca/profile/heluecht>
9 * Copyright (c) 2011-2013 Tobias Diekershoff, Michael Vogel
10 * All rights reserved.
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.
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.
36 /* Twitter Plugin for Friendica
38 * Author: Tobias Diekershoff
39 * tobias.diekershoff@gmx.net
41 * License:3-clause BSD license
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
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.
51 * Add this key pair to your global .htconfig.php or use the admin panel.
53 * $a->config['twitter']['consumerkey'] = 'your consumer_key here';
54 * $a->config['twitter']['consumersecret'] = 'your consumer_secret here';
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".
60 * Requirements: PHP5, curl [Slinky library]
63 require_once('include/enotify.php');
64 require_once("include/socgraph.php");
66 use Friendica\Core\Config;
67 use Friendica\Core\PConfig;
69 define('TWITTER_DEFAULT_POLL_INTERVAL', 5); // given in minutes
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");
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');
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');
108 function twitter_check_item_notification($a, &$notification_data) {
109 $own_id = PConfig::get($notification_data["uid"], 'twitter', 'own_id');
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)
117 $notification_data["profiles"][] = $own_user[0]["url"];
120 function twitter_follow($a, &$contact) {
122 logger("twitter_follow: Check if contact is twitter contact. ".$contact["url"], LOGGER_DEBUG);
124 if (!strstr($contact["url"], "://twitter.com") && !strstr($contact["url"], "@twitter.com"))
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);
131 $uid = $a->user["uid"];
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');
138 require_once("addon/twitter/codebird.php");
140 $cb = \Codebird\Codebird::getInstance();
141 $cb->setConsumerKey($ckey, $csecret);
142 $cb->setToken($otoken, $osecret);
144 $parameters = array();
145 $parameters["screen_name"] = $nickname;
147 $user = $cb->friendships_create($parameters);
149 twitter_fetchuser($a, $uid, $nickname);
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'",
156 $contact["contact"] = $r[0];
159 function twitter_jot_nets(&$a,&$b) {
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>';
172 function twitter_settings_post ($a,$post) {
175 // don't check twitter settings if twitter submit button is not clicked
176 if (!x($_POST,'twitter-submit'))
179 if (isset($_POST['twitter-disconnect'])) {
181 * if the twitter-disconnect checkbox is set, clear the OAuth key/secret pair
182 * from the user configuration
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');
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');
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']));
222 if (!intval($_POST['twitter-mirror']))
223 del_pconfig(local_user(),'twitter','lastid');
225 info(t('Twitter settings updated.') . EOL);
228 function twitter_settings(&$a,&$s) {
231 $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/twitter/twitter.css' . '" media="all" />' . "\r\n";
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 (280 chars only)
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" ' : '');
252 $css = (($enabled) ? '' : '-disabled');
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>';
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>';
262 if ( (!$ckey) && (!$csecret) ) {
264 * no global consumer keys
265 * display warning and skip personal config
267 $s .= '<p>'. t('No consumer key pair for Twitter found. Please contact your site administrator.') .'</p>';
270 * ok we have a consumer key pair now look into the OAuth stuff
272 if ( (!$otoken) && (!$osecret) ) {
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.
279 require_once('library/twitteroauth.php');
280 $connection = new TwitterOAuth($ckey, $csecret);
281 $request_token = $connection->getRequestToken();
282 $token = $request_token['oauth_token'];
284 * make some nice form
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>';
297 * we have an OAuth key / secret pair for the user
298 * so let's give a chance to disable the postings to Twitter
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>';
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>';
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>';
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>';
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>';
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>';
336 $s .= '</div><div class="clear"></div>';
340 function twitter_post_local(&$a, &$b) {
346 if (!local_user() || (local_user() != $b['uid'])) {
350 $twitter_post = intval(PConfig::get(local_user(), 'twitter', 'post'));
351 $twitter_enable = (($twitter_post && x($_REQUEST, 'twitter_enable')) ? intval($_REQUEST['twitter_enable']) : 0);
353 // if API is used, default to the chosen settings
354 if ($b['api_source'] && intval(PConfig::get(local_user(), 'twitter', 'post_by_default'))) {
358 if (!$twitter_enable) {
362 if (strlen($b['postopts'])) {
363 $b['postopts'] .= ',';
366 $b['postopts'] .= 'twitter';
369 function twitter_action($a, $uid, $pid, $action) {
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');
376 require_once("addon/twitter/codebird.php");
378 $cb = \Codebird\Codebird::getInstance();
379 $cb->setConsumerKey($ckey, $csecret);
380 $cb->setToken($otoken, $osecret);
382 $post = array('id' => $pid);
384 logger("twitter_action '".$action."' ID: ".$pid." data: " . print_r($post, true), LOGGER_DATA);
388 // To-Do: $result = $cb->statuses_destroy($post);
391 $result = $cb->favorites_create($post);
394 $result = $cb->favorites_destroy($post);
397 logger("twitter_action '".$action."' send, result: " . print_r($result, true), LOGGER_DEBUG);
400 function twitter_post_hook(&$a,&$b) {
406 require_once("include/network.php");
408 if (!PConfig::get($b["uid"],'twitter','import')) {
409 if($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))
413 if($b['parent'] != $b['id']) {
414 logger("twitter_post_hook: parameter ".print_r($b, true), LOGGER_DATA);
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"]);
422 $r = q("SELECT * FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
423 dbesc($b["thr-parent"]),
427 logger("twitter_post_hook: no parent found ".$b["thr-parent"]);
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;
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"];
443 logger("twitter_post_hook: parent found ".print_r($orig_post, true), LOGGER_DATA);
447 if($b['private'] || !strstr($b['postopts'],'twitter'))
451 if (($b['verb'] == ACTIVITY_POST) && $b['deleted'])
452 twitter_action($a, $b["uid"], substr($orig_post["uri"], 9), "delete");
454 if($b['verb'] == ACTIVITY_LIKE) {
455 logger("twitter_post_hook: parameter 2 ".substr($b["thr-parent"], 9), LOGGER_DEBUG);
457 twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "unlike");
459 twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "like");
463 if($b['deleted'] || ($b['created'] !== $b['edited']))
466 // if post comes from twitter don't send it back
467 if($b['extid'] == NETWORK_TWITTER)
470 if($b['app'] == "Twitter")
473 logger('twitter post invoked');
476 PConfig::load($b['uid'], 'twitter');
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');
483 if($ckey && $csecret && $otoken && $osecret) {
484 logger('twitter: we have customer key and oauth stuff, going to send.', LOGGER_DEBUG);
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']))
490 require_once('library/twitteroauth.php');
491 require_once('include/bbcode.php');
492 $tweet = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
495 require_once("include/plaintext.php");
496 $msgarr = plaintext($a, $b, $max_char, true, 8);
497 $msg = $msgarr["text"];
499 if (($msg == "") && isset($msgarr["title"]))
500 $msg = shortenmsg($msgarr["title"], $max_char - 50);
504 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo"))
505 $msg .= "\n".$msgarr["url"];
507 if (isset($msgarr["image"]) && ($msgarr["type"] != "video"))
508 $image = $msgarr["image"];
510 // and now tweet it :-)
511 if(strlen($msg) && ($image != "")) {
512 $img_str = fetch_url($image);
514 $tempfile = tempnam(get_temppath(), "cache");
515 file_put_contents($tempfile, $img_str);
517 // Twitter had changed something so that the old library doesn't work anymore
518 // so we are using a new library for twitter
520 // Switching completely to this library with all functions
521 require_once("addon/twitter/codebird.php");
523 $cb = \Codebird\Codebird::getInstance();
524 $cb->setConsumerKey($ckey, $csecret);
525 $cb->setToken($otoken, $osecret);
527 $post = array('status' => $msg, 'media[]' => $tempfile);
530 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
532 $result = $cb->statuses_updateWithMedia($post);
535 logger('twitter_post_with_media send, result: ' . print_r($result, true), LOGGER_DEBUG);
538 Config::set("twitter", "application_name", strip_tags($result->source));
540 if ($result->errors || $result->error) {
541 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
543 // Workaround: Remove the picture link so that the post can be reposted without it
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),
556 if(strlen($msg) && ($image == "")) {
559 require_once("include/plaintext.php");
560 $msgarr = plaintext($a, $b, $max_char, true, 8);
561 $msg = $msgarr["text"];
563 if (($msg == "") && isset($msgarr["title"]))
564 $msg = shortenmsg($msgarr["title"], $max_char - 50);
566 if (isset($msgarr["url"]))
567 $msg .= "\n".$msgarr["url"];
569 $url = 'statuses/update';
570 $post = array('status' => $msg, 'weighted_character_count' => 'true');
573 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
575 $result = $tweet->post($url, $post);
576 logger('twitter_post send, result: ' . print_r($result, true), LOGGER_DEBUG);
579 Config::set("twitter", "application_name", strip_tags($result->source));
581 if ($result->errors) {
582 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
584 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($b['uid']));
586 $a->contact = $r[0]["id"];
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),
598 //q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
599 // dbesc("twitter::".$result->id_str),
600 // dbesc($result->text),
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 );
617 function twitter_plugin_admin(&$a, &$o){
618 $t = get_markup_template( "admin.tpl", "addon/twitter/" );
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'))
629 function twitter_cron($a,$b) {
630 $last = Config::get('twitter','last_poll');
632 $poll_interval = intval(Config::get('twitter','poll_interval'));
634 $poll_interval = TWITTER_DEFAULT_POLL_INTERVAL;
637 $next = $last + ($poll_interval * 60);
639 logger('twitter: poll intervall not reached');
643 logger('twitter: cron_start');
645 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'mirror_posts' AND `v` = '1'");
648 logger('twitter: fetching for user '.$rr['uid']);
649 proc_run(PRIORITY_MEDIUM, "addon/twitter/twitter_sync.php", 1, (int)$rr['uid']);
653 $abandon_days = intval(Config::get('system','account_abandon_days'));
654 if ($abandon_days < 1)
657 $abandon_limit = date("Y-m-d H:i:s", time() - $abandon_days * 86400);
659 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1'");
662 if ($abandon_days != 0) {
663 $user = q("SELECT `login_date` FROM `user` WHERE uid=%d AND `login_date` >= '%s'", $rr['uid'], $abandon_limit);
665 logger('abandoned account: timeline from user '.$rr['uid'].' will not be imported');
670 logger('twitter: importing timeline from user '.$rr['uid']);
671 proc_run(PRIORITY_MEDIUM, "addon/twitter/twitter_sync.php", 2, (int)$rr['uid']);
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;
679 $next_contact_check = 0;
681 if($next_contact_check <= time()) {
682 pumpio_getallusers($a, $rr["uid"]);
683 PConfig::set($rr['uid'],'pumpio','contact_check',time());
690 logger('twitter: cron_end');
692 Config::set('twitter','last_poll', time());
695 function twitter_expire($a,$b) {
697 $days = Config::get('twitter', 'expire');
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']));
709 $r = q("DELETE FROM `item` WHERE `deleted` AND `network` = '%s'", dbesc(NETWORK_TWITTER));
712 require_once("include/items.php");
714 logger('twitter_expire: expire_start');
716 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
719 logger('twitter_expire: user '.$rr['uid']);
720 item_expire($rr['uid'], $days, NETWORK_TWITTER, true);
724 logger('twitter_expire: expire_end');
727 function twitter_prepare_body(&$a,&$b) {
728 if ($b["item"]["network"] != NETWORK_TWITTER)
733 require_once("include/plaintext.php");
735 $item["plink"] = $a->get_baseurl()."/display/".$a->user["nickname"]."/".$item["parent"];
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()));
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;
748 if ((strpos($item["body"], $nickname) === false) && (strpos($item["body"], $nicknameplain) === false))
749 $item["body"] = $nickname." ".$item["body"];
753 $msgarr = plaintext($a, $item, $max_char, true, 8);
754 $msg = $msgarr["text"];
756 if (isset($msgarr["url"]) && ($msgarr["type"] != "photo"))
757 $msg .= " ".$msgarr["url"];
759 if (isset($msgarr["image"]))
760 $msg .= " ".$msgarr["image"];
762 $b['html'] = nl2br(htmlspecialchars($msg));
767 * @brief Build the item array for the mirrored post
769 * @param object $a Application class
770 * @param integer $uid User id
771 * @param object $post Twitter object with the post
773 * @return array item data to be posted
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"] = "";
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);
788 $datarray['body'] = "\n".share_header($item['author-name'], $item['author-link'], $item['author-avatar'], "",
789 $item['created'], $item['plink']);
791 $datarray['body'] .= $item['body'].'[/share]';
793 $item = twitter_createpost($a, $uid, $post, array('id' => 0), false, false, false);
795 $datarray['body'] = $item['body'];
798 $datarray["source"] = $item['app'];
799 $datarray["verb"] = $item['verb'];
801 if (isset($item["location"])) {
802 $datarray["location"] = $item["location"];
805 if (isset($item["coord"])) {
806 $datarray["coord"] = $item["coord"];
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');
819 $application_name = Config::get('twitter', 'application_name');
821 if ($application_name == "")
822 $application_name = $a->get_hostname();
824 $has_picture = false;
826 require_once('mod/item.php');
827 require_once('include/items.php');
828 require_once('mod/share.php');
830 require_once('library/twitteroauth.php');
831 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
833 $parameters = array("exclude_replies" => true, "trim_user" => false, "contributor_details" => true, "include_rts" => true, "tweet_mode" => "extended");
835 $first_time = ($lastid == "");
838 $parameters["since_id"] = $lastid;
840 $items = $connection->get('statuses/user_timeline', $parameters);
842 if (!is_array($items))
845 $posts = array_reverse($items);
848 foreach ($posts as $post) {
849 if ($post->id_str > $lastid) {
850 $lastid = $post->id_str;
851 PConfig::set($uid, 'twitter', 'lastid', $lastid);
857 if (!stristr($post->source, $application_name)) {
859 $_SESSION["authenticated"] = true;
860 $_SESSION["uid"] = $uid;
862 $_REQUEST = twitter_do_mirrorpost($a, $uid, $post);
864 logger('twitter: posting for user '.$uid);
870 PConfig::set($uid, 'twitter', 'lastid', $lastid);
873 function twitter_queue_hook(&$a,&$b) {
875 $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
876 dbesc(NETWORK_TWITTER)
881 require_once('include/queue_fn.php');
884 if($x['network'] !== NETWORK_TWITTER)
887 logger('twitter_queue: run');
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",
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');
905 if ($ckey && $csecret && $otoken && $osecret) {
907 logger('twitter_queue: able to post');
909 $z = unserialize($x['content']);
911 require_once("addon/twitter/codebird.php");
913 $cb = \Codebird\Codebird::getInstance();
914 $cb->setConsumerKey($ckey, $csecret);
915 $cb->setToken($otoken, $osecret);
917 if ($z['url'] == "statuses/update")
918 $result = $cb->statuses_update($z['post']);
920 logger('twitter_queue: post result: ' . print_r($result, true), LOGGER_DEBUG);
923 logger('twitter_queue: Send to Twitter failed: "' . print_r($result->errors, true) . '"');
926 remove_queue_item($x['id']);
929 logger("twitter_queue: Error getting tokens for user ".$user['uid']);
932 logger('twitter_queue: delayed');
933 update_queue_time($x['id']);
938 function twitter_fix_avatar($avatar) {
939 require_once("include/Photo.php");
941 $new_avatar = str_replace("_normal.", ".", $avatar);
943 $info = get_photo_info($new_avatar);
945 $new_avatar = $avatar;
950 function twitter_fetch_contact($uid, $contact, $create_user) {
952 if ($contact->id_str == "")
955 $avatar = twitter_fix_avatar($contact->profile_image_url_https);
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));
963 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
964 intval($uid), dbesc("twitter::".$contact->id_str));
966 if(!count($r) && !$create_user)
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);
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)",
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),
987 dbesc("twitter::".$contact->id_str),
988 dbesc($contact->name),
989 dbesc($contact->screen_name),
991 dbesc(NETWORK_TWITTER),
992 intval(CONTACT_IS_FRIEND),
994 dbesc($contact->location),
995 dbesc($contact->description),
999 $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d LIMIT 1",
1000 dbesc("twitter::".$contact->id_str),
1007 $contact_id = $r[0]['id'];
1009 $g = q("SELECT def_gid FROM user WHERE uid = %d LIMIT 1",
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']);
1018 require_once("Photo.php");
1020 $photos = import_profile_photo($avatar, $uid, $contact_id, true);
1023 q("UPDATE `contact` SET `photo` = '%s',
1028 `avatar-date` = '%s'
1033 dbesc(datetime_convert()),
1034 dbesc(datetime_convert()),
1035 dbesc(datetime_convert()),
1040 // update profile photos once every two weeks as we have no notification of when they change.
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'));
1045 // check that we have all the photos, this has been known to fail on occasion
1047 if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro']) || ($update_photo)) {
1049 logger("twitter_fetch_contact: Updating contact ".$contact->screen_name, LOGGER_DEBUG);
1051 require_once("Photo.php");
1053 $photos = import_profile_photo($avatar, $uid, $r[0]['id'], true);
1056 q("UPDATE `contact` SET `photo` = '%s',
1061 `avatar-date` = '%s',
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),
1089 return($r[0]["id"]);
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');
1098 require_once("addon/twitter/codebird.php");
1100 $cb = \Codebird\Codebird::getInstance();
1101 $cb->setConsumerKey($ckey, $csecret);
1102 $cb->setToken($otoken, $osecret);
1104 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1112 $parameters = array();
1114 if ($screen_name != "")
1115 $parameters["screen_name"] = $screen_name;
1118 $parameters["user_id"] = $user_id;
1120 // Fetching user data
1121 $user = $cb->users_show($parameters);
1123 if (!is_object($user))
1126 $contact_id = twitter_fetch_contact($uid, $user, true);
1131 function twitter_expand_entities($a, $body, $item, $no_tags = false, $picture) {
1132 require_once("include/oembed.php");
1133 require_once("include/network.php");
1139 if (isset($item->entities->urls)) {
1145 foreach ($item->entities->urls AS $url) {
1147 $plain = str_replace($url->url, '', $plain);
1149 if ($url->url && $url->expanded_url && $url->display_url) {
1151 $expanded_url = original_url($url->expanded_url);
1153 $oembed_data = oembed_fetch_url($expanded_url);
1155 // Quickfix: Workaround for URL with "[" and "]" in it
1156 if (strpos($expanded_url, "[") || strpos($expanded_url, "]"))
1157 $expanded_url = $url->url;
1160 $type = $oembed_data->type;
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]";
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]",
1176 //$dontincludemedia = true;
1177 } elseif ($oembed_data->type != "link")
1178 $body = str_replace($url->url,
1179 "[url=".$expanded_url."]".$expanded_url."[/url]",
1182 $img_str = fetch_url($expanded_url, true, $redirects, 4);
1184 $tempfile = tempnam(get_temppath(), "cache");
1185 file_put_contents($tempfile, $img_str);
1186 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
1189 if (substr($mime, 0, 6) == "image/") {
1191 $body = str_replace($url->url, "[img]".$expanded_url."[/img]", $body);
1192 //$dontincludemedia = true;
1194 $type = $oembed_data->type;
1195 $footerurl = $expanded_url;
1196 $footerlink = "[url=".$expanded_url."]".$expanded_url."[/url]";
1198 $body = str_replace($url->url, $footerlink, $body);
1204 if ($footerurl != "")
1205 $footer = add_page_info($footerurl, false, $picture);
1207 if (($footerlink != "") && (trim($footer) != "")) {
1208 $removedlink = trim(str_replace($footerlink, "", $body));
1210 if (($removedlink == "") || strstr($body, $removedlink))
1211 $body = $removedlink;
1216 if (($footer == "") && ($picture != ""))
1217 $body .= "\n\n[img]".$picture."[/img]\n";
1218 elseif (($footer == "") && ($picture == ""))
1219 $body = add_page_info_to_body($body);
1222 return array("body" => $body, "tags" => "", "plain" => $plain);
1224 $tags_arr = array();
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);
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);
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);
1242 foreach($tags as $tag) {
1243 if (strstr(trim($tag), " "))
1246 if(strpos($tag,'#') === 0) {
1247 if(strpos($tag,'[url='))
1250 // don't link tags that are already embedded in links
1252 if(preg_match('/\[(.*?)' . preg_quote($tag,'/') . '(.*?)\]/',$body))
1254 if(preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag,'/') . '(.*?)\)/',$body))
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;
1262 } elseif(strpos($tag,'@') === 0) {
1263 if(strpos($tag,'[url='))
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;
1275 $tags = implode($tags_arr, ",");
1278 return array("body" => $body, "tags" => $tags, "plain" => $plain);
1282 * @brief Fetch media entities and add media links to the body
1284 * @param object $post Twitter object with the post
1285 * @param array $postarray Array of the item that is about to be posted
1287 * @return $picture string Returns a a single picture string if it isn't a media post
1289 function twitter_media_entities($post, &$postarray) {
1291 // There are no media entities? So we quit.
1292 if (!is_array($post->extended_entities->media)) {
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)) {
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']);
1309 // This is a pure media post, first search for all media urls
1311 foreach($post->extended_entities->media AS $medium) {
1312 switch($medium->type) {
1314 $media[$medium->url] .= "\n[img]".$medium->media_url_https."[/img]";
1315 $postarray['object-type'] = ACTIVITY_OBJ_IMAGE;
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)) {
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;
1332 // The following code will only be activated for test reasons
1334 // $postarray['body'] .= print_r($medium, true);
1338 // Now we replace the media urls.
1339 foreach ($media AS $key => $value) {
1340 $postarray['body'] = str_replace($key, "\n".$value."\n", $postarray['body']);
1345 function twitter_createpost($a, $uid, $post, $self, $create_user, $only_existing_contact, $noquote) {
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);
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']),
1362 logger("Item with extid ".$postarray['uri']." found.", LOGGER_DEBUG);
1368 if ($post->in_reply_to_status_id_str != "") {
1370 $parent = "twitter::".$post->in_reply_to_status_id_str;
1372 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
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;
1382 $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
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;
1392 $postarray['thr-parent'] = $postarray['uri'];
1393 $postarray['parent-uri'] = $postarray['uri'];
1394 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1399 $own_id = PConfig::get($uid, 'twitter', 'own_id');
1401 if ($post->user->id_str == $own_id) {
1402 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1406 $contactid = $r[0]["id"];
1408 $postarray['owner-name'] = $r[0]["name"];
1409 $postarray['owner-link'] = $r[0]["url"];
1410 $postarray['owner-avatar'] = $r[0]["photo"];
1412 logger("No self contact for user ".$uid, LOGGER_DEBUG);
1416 // Don't create accounts of people who just comment something
1417 $create_user = false;
1419 $postarray['parent-uri'] = $postarray['uri'];
1420 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1423 if ($contactid == 0) {
1424 $contactid = twitter_fetch_contact($uid, $post->user, $create_user);
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);
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);
1438 $postarray['contact-id'] = $contactid;
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);
1447 if ($post->user->protected) {
1448 $postarray['private'] = 1;
1449 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1452 if (is_string($post->full_text)) {
1453 $postarray['body'] = $post->full_text;
1455 $postarray['body'] = $post->text;
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;
1463 // Search for media links
1464 $picture = twitter_media_entities($post, $postarray);
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);
1472 $statustext = $converted["plain"];
1474 if (is_string($post->place->name)) {
1475 $postarray["location"] = $post->place->name;
1477 if (is_string($post->place->full_name)) {
1478 $postarray["location"] = $post->place->full_name;
1480 if (is_array($post->geo->coordinates)) {
1481 $postarray["coord"] = $post->geo->coordinates[0]." ".$post->geo->coordinates[1];
1483 if (is_array($post->coordinates->coordinates)) {
1484 $postarray["coord"] = $post->coordinates->coordinates[1]." ".$post->coordinates->coordinates[0];
1486 if (is_object($post->retweeted_status)) {
1487 $retweet = twitter_createpost($a, $uid, $post->retweeted_status, $self, false, false, $noquote);
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'];
1497 $postarray = $retweet;
1500 if (is_object($post->quoted_status) && !$noquote) {
1501 $quoted = twitter_createpost($a, $uid, $post->quoted_status, $self, false, false, true);
1503 $postarray['body'] = $statustext;
1505 $postarray['body'] .= "\n".share_header($quoted['author-name'], $quoted['author-link'], $quoted['author-avatar'], "",
1506 $quoted['created'], $quoted['plink']);
1508 $postarray['body'] .= $quoted['body'].'[/share]';
1514 function twitter_checknotification($a, $uid, $own_id, $top_item, $postarray) {
1516 // this whole function doesn't seem to work. Needs complete check
1518 $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
1526 if (link_compare($user[0]["url"], $postarray['author-link']))
1529 $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1531 dbesc("twitter::".$own_id)
1534 if(!count($own_user))
1537 // Is it me from twitter?
1538 if (link_compare($own_user[0]["url"], $postarray['author-link']))
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']),
1546 if(count($myconv)) {
1548 foreach($myconv as $conv) {
1549 // now if we find a match, it means we're in this conversation
1551 if(!link_compare($conv['author-link'],$user[0]["url"]) && !link_compare($conv['author-link'],$own_user[0]["url"]))
1554 require_once('include/enotify.php');
1556 $conv_parent = $conv['parent'];
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,
1572 'parent' => $conv_parent,
1575 // only send one notification
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);
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);
1589 $post = $connection->get('statuses/show', $parameters);
1591 if (!count($post)) {
1592 logger("twitter_fetchparentposts: Can't fetch post ".$parameters->id, LOGGER_DEBUG);
1596 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1597 dbesc("twitter::".$post->id_str),
1607 logger("twitter_fetchparentposts: Fetching ".count($posts)." parents", LOGGER_DEBUG);
1609 $posts = array_reverse($posts);
1611 if (count($posts)) {
1612 foreach ($posts as $post) {
1613 $postarray = twitter_createpost($a, $uid, $post, $self, false, false, false);
1615 if (trim($postarray['body']) == "")
1618 $item = item_store($postarray);
1619 $postarray["id"] = $item;
1621 logger('twitter_fetchparentpost: User '.$self["nick"].' posted parent timeline item '.$item);
1623 if ($item && !function_exists("check_item_notification"))
1624 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
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');
1637 logger("twitter_fetchhometimeline: Fetching for user ".$uid, LOGGER_DEBUG);
1639 $application_name = Config::get('twitter', 'application_name');
1641 if ($application_name == "")
1642 $application_name = $a->get_hostname();
1644 require_once('library/twitteroauth.php');
1645 require_once('include/items.php');
1647 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
1649 $own_contact = twitter_fetch_own_contact($a, $uid);
1651 $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1652 intval($own_contact),
1656 $own_id = $r[0]["nick"];
1658 logger("twitter_fetchhometimeline: Own twitter contact not found for user ".$uid, LOGGER_DEBUG);
1662 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1668 logger("twitter_fetchhometimeline: Own contact not found for user ".$uid, LOGGER_DEBUG);
1672 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1675 logger("twitter_fetchhometimeline: Own user not found for user ".$uid, LOGGER_DEBUG);
1679 $parameters = array("exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true, "tweet_mode" => "extended");
1680 //$parameters["count"] = 200;
1683 // Fetching timeline
1684 $lastid = PConfig::get($uid, 'twitter', 'lasthometimelineid');
1686 $first_time = ($lastid == "");
1689 $parameters["since_id"] = $lastid;
1691 $items = $connection->get('statuses/home_timeline', $parameters);
1693 if (!is_array($items)) {
1694 logger("twitter_fetchhometimeline: Error fetching home timeline: ".print_r($items, true), LOGGER_DEBUG);
1698 $posts = array_reverse($items);
1700 logger("twitter_fetchhometimeline: Fetching timeline for user ".$uid." ".sizeof($posts)." items", LOGGER_DEBUG);
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);
1712 if (stristr($post->source, $application_name) && $post->user->screen_name == $own_id) {
1713 logger("twitter_fetchhometimeline: Skip previously sended post", LOGGER_DEBUG);
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);
1722 if ($post->in_reply_to_status_id_str != "")
1723 twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
1725 $postarray = twitter_createpost($a, $uid, $post, $self, $create_user, true, false);
1727 if (trim($postarray['body']) == "")
1730 $item = item_store($postarray);
1731 $postarray["id"] = $item;
1733 logger('twitter_fetchhometimeline: User '.$self["nick"].' posted home timeline item '.$item);
1735 if ($item && !function_exists("check_item_notification"))
1736 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
1740 PConfig::set($uid, 'twitter', 'lasthometimelineid', $lastid);
1742 // Fetching mentions
1743 $lastid = PConfig::get($uid, 'twitter', 'lastmentionid');
1745 $first_time = ($lastid == "");
1748 $parameters["since_id"] = $lastid;
1750 $items = $connection->get('statuses/mentions_timeline', $parameters);
1752 if (!is_array($items)) {
1753 logger("twitter_fetchhometimeline: Error fetching mentions: ".print_r($items, true), LOGGER_DEBUG);
1757 $posts = array_reverse($items);
1759 logger("twitter_fetchhometimeline: Fetching mentions for user ".$uid." ".sizeof($posts)." items", LOGGER_DEBUG);
1761 if (count($posts)) {
1762 foreach ($posts as $post) {
1763 if ($post->id_str > $lastid)
1764 $lastid = $post->id_str;
1769 if ($post->in_reply_to_status_id_str != "")
1770 twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
1772 $postarray = twitter_createpost($a, $uid, $post, $self, false, false, false);
1774 if (trim($postarray['body']) == "")
1777 $item = item_store($postarray);
1778 $postarray["id"] = $item;
1780 if ($item && function_exists("check_item_notification"))
1781 check_item_notification($item, $uid, NOTIFY_TAGSELF);
1783 if (!isset($postarray["parent"]) || ($postarray["parent"] == 0))
1784 $postarray["parent"] = $item;
1786 logger('twitter_fetchhometimeline: User '.$self["nick"].' posted mention timeline item '.$item);
1789 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1790 dbesc($postarray['uri']),
1794 $item = $r[0]['id'];
1795 $parent_id = $r[0]['parent'];
1798 $parent_id = $postarray['parent'];
1800 if (($item != 0) && !function_exists("check_item_notification")) {
1801 require_once('include/enotify.php');
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,
1816 'parent' => $parent_id
1822 PConfig::set($uid, 'twitter', 'lastmentionid', $lastid);
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');
1831 $own_id = PConfig::get($uid, 'twitter', 'own_id');
1835 if ($own_id == "") {
1836 require_once('library/twitteroauth.php');
1838 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
1840 // Fetching user data
1841 $user = $connection->get('account/verify_credentials');
1843 PConfig::set($uid, 'twitter', 'own_id', $user->id_str);
1845 $contact_id = twitter_fetch_contact($uid, $user, true);
1848 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1849 intval($uid), dbesc("twitter::".$own_id));
1851 $contact_id = $r[0]["id"];
1853 del_pconfig($uid, 'twitter', 'own_id');
1857 return($contact_id);
1860 function twitter_is_retweet($a, $uid, $body) {
1861 $body = trim($body);
1863 // Skip if it isn't a pure repeated messages
1864 // Does it start with a share?
1865 if (strpos($body, "[share") > 0)
1868 // Does it end with a share?
1869 if (strlen($body) > (strrpos($body, "[/share]") + 8))
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)
1878 preg_match("/link='(.*?)'/ism", $attributes, $matches);
1879 if ($matches[1] != "")
1880 $link = $matches[1];
1882 preg_match('/link="(.*?)"/ism', $attributes, $matches);
1883 if ($matches[1] != "")
1884 $link = $matches[1];
1886 $id = preg_replace("=https?://twitter.com/(.*)/status/(.*)=ism", "$2", $link);
1890 logger('twitter_is_retweet: Retweeting id '.$id.' for user '.$uid, LOGGER_DEBUG);
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');
1897 require_once('library/twitteroauth.php');
1898 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
1900 $result = $connection->post('statuses/retweet/'.$id);
1902 logger('twitter_is_retweet: result '.print_r($result, true), LOGGER_DEBUG);
1904 return(!isset($result->errors));