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');
65 define('TWITTER_DEFAULT_POLL_INTERVAL', 5); // given in minutes
67 function twitter_install() {
68 // we need some hooks, for the configuration and for sending tweets
69 register_hook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
70 register_hook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
71 register_hook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
72 register_hook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
73 register_hook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
74 register_hook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
75 register_hook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
76 register_hook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
77 register_hook('expire', 'addon/twitter/twitter.php', 'twitter_expire');
78 register_hook('prepare_body', 'addon/twitter/twitter.php', 'twitter_prepare_body');
79 register_hook('check_item_notification','addon/twitter/twitter.php', 'twitter_check_item_notification');
80 logger("installed twitter");
84 function twitter_uninstall() {
85 unregister_hook('connector_settings', 'addon/twitter/twitter.php', 'twitter_settings');
86 unregister_hook('connector_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
87 unregister_hook('post_local', 'addon/twitter/twitter.php', 'twitter_post_local');
88 unregister_hook('notifier_normal', 'addon/twitter/twitter.php', 'twitter_post_hook');
89 unregister_hook('jot_networks', 'addon/twitter/twitter.php', 'twitter_jot_nets');
90 unregister_hook('cron', 'addon/twitter/twitter.php', 'twitter_cron');
91 unregister_hook('queue_predeliver', 'addon/twitter/twitter.php', 'twitter_queue_hook');
92 unregister_hook('follow', 'addon/twitter/twitter.php', 'twitter_follow');
93 unregister_hook('expire', 'addon/twitter/twitter.php', 'twitter_expire');
94 unregister_hook('prepare_body', 'addon/twitter/twitter.php', 'twitter_prepare_body');
95 unregister_hook('check_item_notification','addon/twitter/twitter.php', 'twitter_check_item_notification');
97 // old setting - remove only
98 unregister_hook('post_local_end', 'addon/twitter/twitter.php', 'twitter_post_hook');
99 unregister_hook('plugin_settings', 'addon/twitter/twitter.php', 'twitter_settings');
100 unregister_hook('plugin_settings_post', 'addon/twitter/twitter.php', 'twitter_settings_post');
104 function twitter_check_item_notification($a, &$notification_data) {
105 $own_id = get_pconfig($notification_data["uid"], 'twitter', 'own_id');
107 $own_user = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
108 intval($notification_data["uid"]),
109 dbesc("twitter::".$own_id)
113 $notification_data["profiles"][] = $own_user[0]["url"];
116 function twitter_follow($a, &$contact) {
118 logger("twitter_follow: Check if contact is twitter contact. ".$contact["url"], LOGGER_DEBUG);
120 if (!strstr($contact["url"], "://twitter.com") AND !strstr($contact["url"], "@twitter.com"))
123 // contact seems to be a twitter contact, so continue
124 $nickname = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $contact["url"]);
125 $nickname = str_replace("@twitter.com", "", $nickname);
127 $uid = $a->user["uid"];
129 $ckey = get_config('twitter', 'consumerkey');
130 $csecret = get_config('twitter', 'consumersecret');
131 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
132 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
134 require_once("addon/twitter/codebird.php");
136 $cb = \Codebird\Codebird::getInstance();
137 $cb->setConsumerKey($ckey, $csecret);
138 $cb->setToken($otoken, $osecret);
140 $parameters = array();
141 $parameters["screen_name"] = $nickname;
143 $user = $cb->friendships_create($parameters);
145 twitter_fetchuser($a, $uid, $nickname);
147 $r = q("SELECT name,nick,url,addr,batch,notify,poll,request,confirm,poco,photo,priority,network,alias,pubkey
148 FROM `contact` WHERE `uid` = %d AND `nick` = '%s'",
152 $contact["contact"] = $r[0];
155 function twitter_jot_nets(&$a,&$b) {
159 $tw_post = get_pconfig(local_user(),'twitter','post');
160 if(intval($tw_post) == 1) {
161 $tw_defpost = get_pconfig(local_user(),'twitter','post_by_default');
162 $selected = ((intval($tw_defpost) == 1) ? ' checked="checked" ' : '');
163 $b .= '<div class="profile-jot-net"><input type="checkbox" name="twitter_enable"' . $selected . ' value="1" /> '
164 . t('Post to Twitter') . '</div>';
168 function twitter_settings_post ($a,$post) {
171 // don't check twitter settings if twitter submit button is not clicked
172 if (!x($_POST,'twitter-submit'))
175 if (isset($_POST['twitter-disconnect'])) {
177 * if the twitter-disconnect checkbox is set, clear the OAuth key/secret pair
178 * from the user configuration
180 del_pconfig(local_user(), 'twitter', 'consumerkey');
181 del_pconfig(local_user(), 'twitter', 'consumersecret');
182 del_pconfig(local_user(), 'twitter', 'oauthtoken');
183 del_pconfig(local_user(), 'twitter', 'oauthsecret');
184 del_pconfig(local_user(), 'twitter', 'post');
185 del_pconfig(local_user(), 'twitter', 'post_by_default');
186 del_pconfig(local_user(), 'twitter', 'lastid');
187 del_pconfig(local_user(), 'twitter', 'mirror_posts');
188 del_pconfig(local_user(), 'twitter', 'import');
189 del_pconfig(local_user(), 'twitter', 'create_user');
190 del_pconfig(local_user(), 'twitter', 'own_id');
192 if (isset($_POST['twitter-pin'])) {
193 // if the user supplied us with a PIN from Twitter, let the magic of OAuth happen
194 logger('got a Twitter PIN');
195 require_once('library/twitteroauth.php');
196 $ckey = get_config('twitter', 'consumerkey');
197 $csecret = get_config('twitter', 'consumersecret');
198 // the token and secret for which the PIN was generated were hidden in the settings
199 // form as token and token2, we need a new connection to Twitter using these token
200 // and secret to request a Access Token with the PIN
201 $connection = new TwitterOAuth($ckey, $csecret, $_POST['twitter-token'], $_POST['twitter-token2']);
202 $token = $connection->getAccessToken( $_POST['twitter-pin'] );
203 // ok, now that we have the Access Token, save them in the user config
204 set_pconfig(local_user(),'twitter', 'oauthtoken', $token['oauth_token']);
205 set_pconfig(local_user(),'twitter', 'oauthsecret', $token['oauth_token_secret']);
206 set_pconfig(local_user(),'twitter', 'post', 1);
207 // reload the Addon Settings page, if we don't do it see Bug #42
208 goaway($a->get_baseurl().'/settings/connectors');
210 // if no PIN is supplied in the POST variables, the user has changed the setting
211 // to post a tweet for every new __public__ posting to the wall
212 set_pconfig(local_user(),'twitter','post',intval($_POST['twitter-enable']));
213 set_pconfig(local_user(),'twitter','post_by_default',intval($_POST['twitter-default']));
214 set_pconfig(local_user(), 'twitter', 'mirror_posts', intval($_POST['twitter-mirror']));
215 set_pconfig(local_user(), 'twitter', 'import', intval($_POST['twitter-import']));
216 set_pconfig(local_user(), 'twitter', 'create_user', intval($_POST['twitter-create_user']));
218 if (!intval($_POST['twitter-mirror']))
219 del_pconfig(local_user(),'twitter','lastid');
221 info(t('Twitter settings updated.') . EOL);
224 function twitter_settings(&$a,&$s) {
227 $a->page['htmlhead'] .= '<link rel="stylesheet" type="text/css" href="' . $a->get_baseurl() . '/addon/twitter/twitter.css' . '" media="all" />' . "\r\n";
229 * 1) Check that we have global consumer key & secret
230 * 2) If no OAuthtoken & stuff is present, generate button to get some
231 * 3) Checkbox for "Send public notices (140 chars only)
233 $ckey = get_config('twitter', 'consumerkey' );
234 $csecret = get_config('twitter', 'consumersecret' );
235 $otoken = get_pconfig(local_user(), 'twitter', 'oauthtoken' );
236 $osecret = get_pconfig(local_user(), 'twitter', 'oauthsecret' );
237 $enabled = get_pconfig(local_user(), 'twitter', 'post');
238 $checked = (($enabled) ? ' checked="checked" ' : '');
239 $defenabled = get_pconfig(local_user(),'twitter','post_by_default');
240 $defchecked = (($defenabled) ? ' checked="checked" ' : '');
241 $mirrorenabled = get_pconfig(local_user(),'twitter','mirror_posts');
242 $mirrorchecked = (($mirrorenabled) ? ' checked="checked" ' : '');
243 $importenabled = get_pconfig(local_user(),'twitter','import');
244 $importchecked = (($importenabled) ? ' checked="checked" ' : '');
245 $create_userenabled = get_pconfig(local_user(),'twitter','create_user');
246 $create_userchecked = (($create_userenabled) ? ' checked="checked" ' : '');
248 $css = (($enabled) ? '' : '-disabled');
250 $s .= '<span id="settings_twitter_inflated" class="settings-block fakelink" style="display: block;" onclick="openClose(\'settings_twitter_expanded\'); openClose(\'settings_twitter_inflated\');">';
251 $s .= '<img class="connector'.$css.'" src="images/twitter.png" /><h3 class="connector">'. t('Twitter Import/Export/Mirror').'</h3>';
253 $s .= '<div id="settings_twitter_expanded" class="settings-block" style="display: none;">';
254 $s .= '<span class="fakelink" 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>';
258 if ( (!$ckey) && (!$csecret) ) {
260 * no global consumer keys
261 * display warning and skip personal config
263 $s .= '<p>'. t('No consumer key pair for Twitter found. Please contact your site administrator.') .'</p>';
266 * ok we have a consumer key pair now look into the OAuth stuff
268 if ( (!$otoken) && (!$osecret) ) {
270 * the user has not yet connected the account to twitter...
271 * get a temporary OAuth key/secret pair and display a button with
272 * which the user can request a PIN to connect the account to a
273 * account at Twitter.
275 require_once('library/twitteroauth.php');
276 $connection = new TwitterOAuth($ckey, $csecret);
277 $request_token = $connection->getRequestToken();
278 $token = $request_token['oauth_token'];
280 * make some nice form
282 $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>';
283 $s .= '<a href="'.$connection->getAuthorizeURL($token).'" target="_twitter"><img src="addon/twitter/lighter.png" alt="'.t('Log in with Twitter').'"></a>';
284 $s .= '<div id="twitter-pin-wrapper">';
285 $s .= '<label id="twitter-pin-label" for="twitter-pin">'. t('Copy the PIN from Twitter here') .'</label>';
286 $s .= '<input id="twitter-pin" type="text" name="twitter-pin" />';
287 $s .= '<input id="twitter-token" type="hidden" name="twitter-token" value="'.$token.'" />';
288 $s .= '<input id="twitter-token2" type="hidden" name="twitter-token2" value="'.$request_token['oauth_token_secret'].'" />';
289 $s .= '</div><div class="clear"></div>';
290 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . t('Save Settings') . '" /></div>';
293 * we have an OAuth key / secret pair for the user
294 * so let's give a chance to disable the postings to Twitter
296 require_once('library/twitteroauth.php');
297 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
298 $details = $connection->get('account/verify_credentials');
299 $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>';
300 $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>';
301 if ($a->user['hidewall']) {
302 $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>';
304 $s .= '<div id="twitter-enable-wrapper">';
305 $s .= '<label id="twitter-enable-label" for="twitter-checkbox">'. t('Allow posting to Twitter'). '</label>';
306 $s .= '<input id="twitter-checkbox" type="checkbox" name="twitter-enable" value="1" ' . $checked . '/>';
307 $s .= '<div class="clear"></div>';
308 $s .= '<label id="twitter-default-label" for="twitter-default">'. t('Send public postings to Twitter by default') .'</label>';
309 $s .= '<input id="twitter-default" type="checkbox" name="twitter-default" value="1" ' . $defchecked . '/>';
310 $s .= '<div class="clear"></div>';
312 $s .= '<label id="twitter-mirror-label" for="twitter-mirror">'.t('Mirror all posts from twitter that are no replies').'</label>';
313 $s .= '<input id="twitter-mirror" type="checkbox" name="twitter-mirror" value="1" '. $mirrorchecked . '/>';
314 $s .= '<div class="clear"></div>';
317 $s .= '<label id="twitter-import-label" for="twitter-import">'.t('Import the remote timeline').'</label>';
318 $s .= '<input id="twitter-import" type="checkbox" name="twitter-import" value="1" '. $importchecked . '/>';
319 $s .= '<div class="clear"></div>';
321 $s .= '<label id="twitter-create_user-label" for="twitter-create_user">'.t('Automatically create contacts').'</label>';
322 $s .= '<input id="twitter-create_user" type="checkbox" name="twitter-create_user" value="1" '. $create_userchecked . '/>';
323 $s .= '<div class="clear"></div>';
325 $s .= '<div id="twitter-disconnect-wrapper">';
326 $s .= '<label id="twitter-disconnect-label" for="twitter-disconnect">'. t('Clear OAuth configuration') .'</label>';
327 $s .= '<input id="twitter-disconnect" type="checkbox" name="twitter-disconnect" value="1" />';
328 $s .= '</div><div class="clear"></div>';
329 $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="twitter-submit" class="settings-submit" value="' . t('Save Settings') . '" /></div>';
332 $s .= '</div><div class="clear"></div>';
336 function twitter_post_local(&$a,&$b) {
341 if((local_user()) && (local_user() == $b['uid']) && (! $b['private']) && (! $b['parent']) ) {
343 $twitter_post = intval(get_pconfig(local_user(),'twitter','post'));
344 $twitter_enable = (($twitter_post && x($_REQUEST,'twitter_enable')) ? intval($_REQUEST['twitter_enable']) : 0);
346 // if API is used, default to the chosen settings
347 if($_REQUEST['api_source'] && intval(get_pconfig(local_user(),'twitter','post_by_default')))
350 if(! $twitter_enable)
353 if(strlen($b['postopts']))
354 $b['postopts'] .= ',';
355 $b['postopts'] .= 'twitter';
359 function twitter_action($a, $uid, $pid, $action) {
361 $ckey = get_config('twitter', 'consumerkey');
362 $csecret = get_config('twitter', 'consumersecret');
363 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
364 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
366 require_once("addon/twitter/codebird.php");
368 $cb = \Codebird\Codebird::getInstance();
369 $cb->setConsumerKey($ckey, $csecret);
370 $cb->setToken($otoken, $osecret);
372 $post = array('id' => $pid);
374 logger("twitter_action '".$action."' ID: ".$pid." data: " . print_r($post, true), LOGGER_DATA);
378 // To-Do: $result = $cb->statuses_destroy($post);
381 $result = $cb->favorites_create($post);
384 $result = $cb->favorites_destroy($post);
387 logger("twitter_action '".$action."' send, result: " . print_r($result, true), LOGGER_DEBUG);
390 function twitter_post_hook(&$a,&$b) {
396 require_once("include/network.php");
398 if (!get_pconfig($b["uid"],'twitter','import')) {
399 if($b['deleted'] || $b['private'] || ($b['created'] !== $b['edited']))
403 if($b['parent'] != $b['id']) {
404 logger("twitter_post_hook: parameter ".print_r($b, true), LOGGER_DATA);
406 // Looking if its a reply to a twitter post
407 if ((substr($b["parent-uri"], 0, 9) != "twitter::") AND (substr($b["extid"], 0, 9) != "twitter::") AND (substr($b["thr-parent"], 0, 9) != "twitter::")) {
408 logger("twitter_post_hook: no twitter post ".$b["parent"]);
412 $r = q("SELECT * FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
413 dbesc($b["thr-parent"]),
417 logger("twitter_post_hook: no parent found ".$b["thr-parent"]);
425 $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
426 $nickname = "@[url=".$orig_post["author-link"]."]".$nicknameplain."[/url]";
427 $nicknameplain = "@".$nicknameplain;
429 logger("twitter_post_hook: comparing ".$nickname." and ".$nicknameplain." with ".$b["body"], LOGGER_DEBUG);
430 if ((strpos($b["body"], $nickname) === false) AND (strpos($b["body"], $nicknameplain) === false))
431 $b["body"] = $nickname." ".$b["body"];
433 logger("twitter_post_hook: parent found ".print_r($orig_post, true), LOGGER_DATA);
437 if($b['private'] OR !strstr($b['postopts'],'twitter'))
441 if (($b['verb'] == ACTIVITY_POST) AND $b['deleted'])
442 twitter_action($a, $b["uid"], substr($orig_post["uri"], 9), "delete");
444 if($b['verb'] == ACTIVITY_LIKE) {
445 logger("twitter_post_hook: parameter 2 ".substr($b["thr-parent"], 9), LOGGER_DEBUG);
447 twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "unlike");
449 twitter_action($a, $b["uid"], substr($b["thr-parent"], 9), "like");
453 if($b['deleted'] || ($b['created'] !== $b['edited']))
456 // if post comes from twitter don't send it back
457 if($b['extid'] == NETWORK_TWITTER)
460 if($b['app'] == "Twitter")
463 logger('twitter post invoked');
466 load_pconfig($b['uid'], 'twitter');
468 $ckey = get_config('twitter', 'consumerkey');
469 $csecret = get_config('twitter', 'consumersecret');
470 $otoken = get_pconfig($b['uid'], 'twitter', 'oauthtoken');
471 $osecret = get_pconfig($b['uid'], 'twitter', 'oauthsecret');
473 if($ckey && $csecret && $otoken && $osecret) {
474 logger('twitter: we have customer key and oauth stuff, going to send.', LOGGER_DEBUG);
476 // If it's a repeated message from twitter then do a native retweet and exit
477 if (twitter_is_retweet($a, $b['uid'], $b['body']))
480 require_once('library/twitteroauth.php');
481 require_once('include/bbcode.php');
482 $tweet = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
485 require_once("include/plaintext.php");
486 $msgarr = plaintext($a, $b, $max_char, true, 8);
487 $msg = $msgarr["text"];
489 if (($msg == "") AND isset($msgarr["title"]))
490 $msg = shortenmsg($msgarr["title"], $max_char - 50);
494 if (isset($msgarr["url"]) AND ($msgarr["type"] != "photo"))
495 $msg .= "\n".$msgarr["url"];
496 elseif (isset($msgarr["image"]) AND ($msgarr["type"] != "video"))
497 $image = $msgarr["image"];
499 // and now tweet it :-)
500 if(strlen($msg) and ($image != "")) {
501 $img_str = fetch_url($image);
503 $tempfile = tempnam(get_temppath(), "cache");
504 file_put_contents($tempfile, $img_str);
506 // Twitter had changed something so that the old library doesn't work anymore
507 // so we are using a new library for twitter
509 // Switching completely to this library with all functions
510 require_once("addon/twitter/codebird.php");
512 $cb = \Codebird\Codebird::getInstance();
513 $cb->setConsumerKey($ckey, $csecret);
514 $cb->setToken($otoken, $osecret);
516 $post = array('status' => $msg, 'media[]' => $tempfile);
519 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
521 $result = $cb->statuses_updateWithMedia($post);
524 logger('twitter_post_with_media send, result: ' . print_r($result, true), LOGGER_DEBUG);
527 set_config("twitter", "application_name", strip_tags($result->source));
529 if ($result->errors OR $result->error) {
530 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
532 // Workaround: Remove the picture link so that the post can be reposted without it
535 } elseif ($iscomment) {
536 logger('twitter_post: Update extid '.$result->id_str." for post id ".$b['id']);
537 q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
538 dbesc("twitter::".$result->id_str),
539 dbesc($result->text),
545 if(strlen($msg) and ($image == "")) {
546 $url = 'statuses/update';
547 $post = array('status' => $msg);
550 $post["in_reply_to_status_id"] = substr($orig_post["uri"], 9);
552 $result = $tweet->post($url, $post);
553 logger('twitter_post send, result: ' . print_r($result, true), LOGGER_DEBUG);
556 set_config("twitter", "application_name", strip_tags($result->source));
558 if ($result->errors) {
559 logger('Send to Twitter failed: "' . print_r($result->errors, true) . '"');
561 $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($b['uid']));
563 $a->contact = $r[0]["id"];
565 $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $post));
566 require_once('include/queue_fn.php');
567 add_to_queue($a->contact,NETWORK_TWITTER,$s);
568 notice(t('Twitter post failed. Queued for retry.').EOL);
569 } elseif ($iscomment) {
570 logger('twitter_post: Update extid '.$result->id_str." for post id ".$b['id']);
571 q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d",
572 dbesc("twitter::".$result->id_str),
575 //q("UPDATE `item` SET `extid` = '%s', `body` = '%s' WHERE `id` = %d",
576 // dbesc("twitter::".$result->id_str),
577 // dbesc($result->text),
585 function twitter_plugin_admin_post(&$a){
586 $consumerkey = ((x($_POST,'consumerkey')) ? notags(trim($_POST['consumerkey'])) : '');
587 $consumersecret = ((x($_POST,'consumersecret')) ? notags(trim($_POST['consumersecret'])): '');
588 $applicationname = ((x($_POST, 'applicationname')) ? notags(trim($_POST['applicationname'])):'');
589 set_config('twitter','consumerkey',$consumerkey);
590 set_config('twitter','consumersecret',$consumersecret);
591 //set_config('twitter','application_name',$applicationname);
592 info( t('Settings updated.'). EOL );
594 function twitter_plugin_admin(&$a, &$o){
595 $t = get_markup_template( "admin.tpl", "addon/twitter/" );
597 $o = replace_macros($t, array(
598 '$submit' => t('Save Settings'),
599 // name, label, value, help, [extra values]
600 '$consumerkey' => array('consumerkey', t('Consumer key'), get_config('twitter', 'consumerkey' ), ''),
601 '$consumersecret' => array('consumersecret', t('Consumer secret'), get_config('twitter', 'consumersecret' ), ''),
602 //'$applicationname' => array('applicationname', t('Name of the Twitter Application'), get_config('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'))
606 function twitter_cron($a,$b) {
607 $last = get_config('twitter','last_poll');
609 $poll_interval = intval(get_config('twitter','poll_interval'));
611 $poll_interval = TWITTER_DEFAULT_POLL_INTERVAL;
614 $next = $last + ($poll_interval * 60);
616 logger('twitter: poll intervall not reached');
620 logger('twitter: cron_start');
622 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'mirror_posts' AND `v` = '1' ORDER BY RAND()");
625 logger('twitter: fetching for user '.$rr['uid']);
626 twitter_fetchtimeline($a, $rr['uid']);
630 $abandon_days = intval(get_config('system','account_abandon_days'));
631 if ($abandon_days < 1)
634 $abandon_limit = date("Y-m-d H:i:s", time() - $abandon_days * 86400);
636 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
639 if ($abandon_days != 0) {
640 $user = q("SELECT `login_date` FROM `user` WHERE uid=%d AND `login_date` >= '%s'", $rr['uid'], $abandon_limit);
642 logger('abandoned account: timeline from user '.$rr['uid'].' will not be imported');
647 logger('twitter: importing timeline from user '.$rr['uid']);
648 twitter_fetchhometimeline($a, $rr["uid"]);
652 // check for new contacts once a day
653 $last_contact_check = get_pconfig($rr['uid'],'pumpio','contact_check');
654 if($last_contact_check)
655 $next_contact_check = $last_contact_check + 86400;
657 $next_contact_check = 0;
659 if($next_contact_check <= time()) {
660 pumpio_getallusers($a, $rr["uid"]);
661 set_pconfig($rr['uid'],'pumpio','contact_check',time());
668 logger('twitter: cron_end');
670 set_config('twitter','last_poll', time());
673 function twitter_expire($a,$b) {
675 $days = get_config('twitter', 'expire');
680 $r = q("DELETE FROM `item` WHERE `deleted` AND `network` = '%s'", dbesc(NETWORK_TWITTER));
682 require_once("include/items.php");
684 logger('twitter_expire: expire_start');
686 $r = q("SELECT * FROM `pconfig` WHERE `cat` = 'twitter' AND `k` = 'import' AND `v` = '1' ORDER BY RAND()");
689 logger('twitter_expire: user '.$rr['uid']);
690 item_expire($rr['uid'], $days, NETWORK_TWITTER, true);
694 logger('twitter_expire: expire_end');
697 function twitter_prepare_body(&$a,&$b) {
698 if ($b["item"]["network"] != NETWORK_TWITTER)
703 require_once("include/plaintext.php");
705 $item["plink"] = $a->get_baseurl()."/display/".$a->user["nickname"]."/".$item["parent"];
707 $r = q("SELECT `author-link` FROM item WHERE item.uri = '%s' AND item.uid = %d LIMIT 1",
708 dbesc($item["thr-parent"]),
709 intval(local_user()));
714 $nicknameplain = preg_replace("=https?://twitter.com/(.*)=ism", "$1", $orig_post["author-link"]);
715 $nickname = "@[url=".$orig_post["author-link"]."]".$nicknameplain."[/url]";
716 $nicknameplain = "@".$nicknameplain;
718 if ((strpos($item["body"], $nickname) === false) AND (strpos($item["body"], $nicknameplain) === false))
719 $item["body"] = $nickname." ".$item["body"];
723 $msgarr = plaintext($a, $item, $max_char, true, 8);
724 $msg = $msgarr["text"];
726 if (isset($msgarr["url"]) AND ($msgarr["type"] != "photo"))
727 $msg .= " ".$msgarr["url"];
729 if (isset($msgarr["image"]))
730 $msg .= " ".$msgarr["image"];
732 $b['html'] = nl2br(htmlspecialchars($msg));
736 function twitter_fetchtimeline($a, $uid) {
737 $ckey = get_config('twitter', 'consumerkey');
738 $csecret = get_config('twitter', 'consumersecret');
739 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
740 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
741 $lastid = get_pconfig($uid, 'twitter', 'lastid');
743 $application_name = get_config('twitter', 'application_name');
745 if ($application_name == "")
746 $application_name = $a->get_hostname();
748 $has_picture = false;
750 require_once('mod/item.php');
751 require_once('include/items.php');
752 require_once('mod/share.php');
754 require_once('library/twitteroauth.php');
755 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
757 $parameters = array("exclude_replies" => true, "trim_user" => false, "contributor_details" => true, "include_rts" => true);
759 $first_time = ($lastid == "");
762 $parameters["since_id"] = $lastid;
764 $items = $connection->get('statuses/user_timeline', $parameters);
766 if (!is_array($items))
769 $posts = array_reverse($items);
772 foreach ($posts as $post) {
773 if ($post->id_str > $lastid)
774 $lastid = $post->id_str;
779 if (!stristr($post->source, $application_name)) {
780 $_SESSION["authenticated"] = true;
781 $_SESSION["uid"] = $uid;
784 $_REQUEST["type"] = "wall";
785 $_REQUEST["api_source"] = true;
786 $_REQUEST["profile_uid"] = $uid;
787 //$_REQUEST["source"] = "Twitter";
788 $_REQUEST["source"] = $post->source;
789 $_REQUEST["extid"] = NETWORK_TWITTER;
791 //$_REQUEST["date"] = $post->created_at;
793 $_REQUEST["title"] = "";
795 if (is_object($post->retweeted_status)) {
797 $_REQUEST['body'] = $post->retweeted_status->text;
802 if (is_array($post->retweeted_status->entities->media)) {
803 foreach($post->retweeted_status->entities->media AS $media) {
804 switch($media->type) {
806 //$_REQUEST['body'] = str_replace($media->url, "\n\n[img]".$media->media_url_https."[/img]\n", $_REQUEST['body']);
807 //$has_picture = true;
808 $_REQUEST['body'] = str_replace($media->url, "", $_REQUEST['body']);
809 $picture = $media->media_url_https;
815 $converted = twitter_expand_entities($a, $_REQUEST['body'], $post->retweeted_status, true, $picture);
816 $_REQUEST['body'] = $converted["body"];
818 if (function_exists("share_header"))
819 $_REQUEST['body'] = share_header($post->retweeted_status->user->name, "https://twitter.com/".$post->retweeted_status->user->screen_name,
820 $post->retweeted_status->user->profile_image_url_https, "",
821 datetime_convert('UTC','UTC',$post->retweeted_status->created_at),
822 "https://twitter.com/".$post->retweeted_status->user->screen_name."/status/".$post->retweeted_status->id_str).
825 $_REQUEST['body'] = "[share author='".$post->retweeted_status->user->name.
826 "' profile='https://twitter.com/".$post->retweeted_status->user->screen_name.
827 "' avatar='".$post->retweeted_status->user->profile_image_url_https.
828 "' posted='".datetime_convert('UTC','UTC',$post->retweeted_status->created_at).
829 "' link='https://twitter.com/".$post->retweeted_status->user->screen_name."/status/".$post->retweeted_status->id_str."']".
832 $_REQUEST['body'] .= "[/share]";
834 $_REQUEST["body"] = $post->text;
838 if (is_array($post->entities->media)) {
839 foreach($post->entities->media AS $media) {
840 switch($media->type) {
842 //$_REQUEST['body'] = str_replace($media->url, "\n\n[img]".$media->media_url_https."[/img]\n", $_REQUEST['body']);
843 //$has_picture = true;
844 $_REQUEST['body'] = str_replace($media->url, "", $_REQUEST['body']);
845 $picture = $media->media_url_https;
851 $converted = twitter_expand_entities($a, $_REQUEST["body"], $post, true, $picture);
852 $_REQUEST['body'] = $converted["body"];
855 if (is_string($post->place->name))
856 $_REQUEST["location"] = $post->place->name;
858 if (is_string($post->place->full_name))
859 $_REQUEST["location"] = $post->place->full_name;
861 if (is_array($post->geo->coordinates))
862 $_REQUEST["coord"] = $post->geo->coordinates[0]." ".$post->geo->coordinates[1];
864 if (is_array($post->coordinates->coordinates))
865 $_REQUEST["coord"] = $post->coordinates->coordinates[1]." ".$post->coordinates->coordinates[0];
867 //print_r($_REQUEST);
868 logger('twitter: posting for user '.$uid);
870 // require_once('mod/item.php');
876 set_pconfig($uid, 'twitter', 'lastid', $lastid);
879 function twitter_queue_hook(&$a,&$b) {
881 $qi = q("SELECT * FROM `queue` WHERE `network` = '%s'",
882 dbesc(NETWORK_TWITTER)
887 require_once('include/queue_fn.php');
890 if($x['network'] !== NETWORK_TWITTER)
893 logger('twitter_queue: run');
895 $r = q("SELECT `user`.* FROM `user` LEFT JOIN `contact` on `contact`.`uid` = `user`.`uid`
896 WHERE `contact`.`self` = 1 AND `contact`.`id` = %d LIMIT 1",
904 $ckey = get_config('twitter', 'consumerkey');
905 $csecret = get_config('twitter', 'consumersecret');
906 $otoken = get_pconfig($user['uid'], 'twitter', 'oauthtoken');
907 $osecret = get_pconfig($user['uid'], 'twitter', 'oauthsecret');
911 if ($ckey AND $csecret AND $otoken AND $osecret) {
913 logger('twitter_queue: able to post');
915 $z = unserialize($x['content']);
917 require_once("addon/twitter/codebird.php");
919 $cb = \Codebird\Codebird::getInstance();
920 $cb->setConsumerKey($ckey, $csecret);
921 $cb->setToken($otoken, $osecret);
923 if ($z['url'] == "statuses/update")
924 $result = $cb->statuses_update($z['post']);
926 logger('twitter_queue: post result: ' . print_r($result, true), LOGGER_DEBUG);
929 logger('twitter_queue: Send to Twitter failed: "' . print_r($result->errors, true) . '"');
932 remove_queue_item($x['id']);
935 logger("twitter_queue: Error getting tokens for user ".$user['uid']);
938 logger('twitter_queue: delayed');
939 update_queue_time($x['id']);
944 function twitter_fix_avatar($avatar) {
945 require_once("include/Photo.php");
947 $new_avatar = str_replace("_normal.", ".", $avatar);
949 $info = get_photo_info($new_avatar);
951 $new_avatar = $avatar;
956 function twitter_fetch_contact($uid, $contact, $create_user) {
958 if ($contact->id_str == "")
961 $avatar = twitter_fix_avatar($contact->profile_image_url_https);
963 if (function_exists("update_gcontact"))
964 update_gcontact(array("url" => "https://twitter.com/".$contact->screen_name,
965 "network" => NETWORK_TWITTER, "photo" => $avatar, "hide" => true,
966 "name" => $contact->name, "nick" => $contact->screen_name,
967 "location" => $contact->location, "about" => $contact->description,
968 "addr" => $contact->screen_name."@twitter.com", "generation" => 2));
971 $r = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1",
972 dbesc(normalise_link("https://twitter.com/".$contact->screen_name)));
975 q("INSERT INTO unique_contacts (url, name, nick, avatar) VALUES ('%s', '%s', '%s', '%s')",
976 dbesc(normalise_link("https://twitter.com/".$contact->screen_name)),
977 dbesc($contact->name),
978 dbesc($contact->screen_name),
981 q("UPDATE unique_contacts SET name = '%s', nick = '%s', avatar = '%s' WHERE url = '%s'",
982 dbesc($contact->name),
983 dbesc($contact->screen_name),
985 dbesc(normalise_link("https://twitter.com/".$contact->screen_name)));
987 if (DB_UPDATE_VERSION >= "1177")
988 q("UPDATE `unique_contacts` SET `location` = '%s', `about` = '%s' WHERE url = '%s'",
989 dbesc($contact->location),
990 dbesc($contact->description),
991 dbesc(normalise_link("https://twitter.com/".$contact->screen_name)));
994 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
995 intval($uid), dbesc("twitter::".$contact->id_str));
997 if(!count($r) AND !$create_user)
1000 if (count($r) AND ($r[0]["readonly"] OR $r[0]["blocked"])) {
1001 logger("twitter_fetch_contact: Contact '".$r[0]["nick"]."' is blocked or readonly.", LOGGER_DEBUG);
1006 // create contact record
1007 q("INSERT INTO `contact` ( `uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`,
1008 `name`, `nick`, `photo`, `network`, `rel`, `priority`,
1009 `writable`, `blocked`, `readonly`, `pending` )
1010 VALUES ( %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, 0, 0, 0) ",
1012 dbesc(datetime_convert()),
1013 dbesc("https://twitter.com/".$contact->screen_name),
1014 dbesc(normalise_link("https://twitter.com/".$contact->screen_name)),
1015 dbesc($contact->screen_name."@twitter.com"),
1016 dbesc("twitter::".$contact->id_str),
1018 dbesc("twitter::".$contact->id_str),
1019 dbesc($contact->name),
1020 dbesc($contact->screen_name),
1022 dbesc(NETWORK_TWITTER),
1023 intval(CONTACT_IS_FRIEND),
1028 $r = q("SELECT * FROM `contact` WHERE `alias` = '%s' AND `uid` = %d LIMIT 1",
1029 dbesc("twitter::".$contact->id_str),
1036 $contact_id = $r[0]['id'];
1038 $g = q("SELECT def_gid FROM user WHERE uid = %d LIMIT 1",
1042 if($g && intval($g[0]['def_gid'])) {
1043 require_once('include/group.php');
1044 group_add_member($uid,'',$contact_id,$g[0]['def_gid']);
1047 require_once("Photo.php");
1049 $photos = import_profile_photo($avatar,$uid,$contact_id);
1051 q("UPDATE `contact` SET `photo` = '%s',
1056 `avatar-date` = '%s'
1061 dbesc(datetime_convert()),
1062 dbesc(datetime_convert()),
1063 dbesc(datetime_convert()),
1067 if (DB_UPDATE_VERSION >= "1177")
1068 q("UPDATE `contact` SET `location` = '%s',
1071 dbesc($contact->location),
1072 dbesc($contact->description),
1077 // update profile photos once every two weeks as we have no notification of when they change.
1079 //$update_photo = (($r[0]['avatar-date'] < datetime_convert('','','now -2 days')) ? true : false);
1080 $update_photo = ($r[0]['avatar-date'] < datetime_convert('','','now -12 hours'));
1082 // check that we have all the photos, this has been known to fail on occasion
1084 if((! $r[0]['photo']) || (! $r[0]['thumb']) || (! $r[0]['micro']) || ($update_photo)) {
1086 logger("twitter_fetch_contact: Updating contact ".$contact->screen_name, LOGGER_DEBUG);
1088 require_once("Photo.php");
1090 $photos = import_profile_photo($avatar, $uid, $r[0]['id']);
1092 q("UPDATE `contact` SET `photo` = '%s',
1097 `avatar-date` = '%s',
1107 dbesc(datetime_convert()),
1108 dbesc(datetime_convert()),
1109 dbesc(datetime_convert()),
1110 dbesc("https://twitter.com/".$contact->screen_name),
1111 dbesc(normalise_link("https://twitter.com/".$contact->screen_name)),
1112 dbesc($contact->screen_name."@twitter.com"),
1113 dbesc($contact->name),
1114 dbesc($contact->screen_name),
1118 if (DB_UPDATE_VERSION >= "1177")
1119 q("UPDATE `contact` SET `location` = '%s',
1122 dbesc($contact->location),
1123 dbesc($contact->description),
1129 return($r[0]["id"]);
1132 function twitter_fetchuser($a, $uid, $screen_name = "", $user_id = "") {
1133 $ckey = get_config('twitter', 'consumerkey');
1134 $csecret = get_config('twitter', 'consumersecret');
1135 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
1136 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
1138 require_once("addon/twitter/codebird.php");
1140 $cb = \Codebird\Codebird::getInstance();
1141 $cb->setConsumerKey($ckey, $csecret);
1142 $cb->setToken($otoken, $osecret);
1144 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1152 $parameters = array();
1154 if ($screen_name != "")
1155 $parameters["screen_name"] = $screen_name;
1158 $parameters["user_id"] = $user_id;
1160 // Fetching user data
1161 $user = $cb->users_show($parameters);
1163 if (!is_object($user))
1166 $contact_id = twitter_fetch_contact($uid, $user, true);
1171 function twitter_expand_entities($a, $body, $item, $no_tags = false, $picture) {
1172 require_once("include/oembed.php");
1173 require_once("include/network.php");
1177 if (isset($item->entities->urls)) {
1183 foreach ($item->entities->urls AS $url) {
1184 if ($url->url AND $url->expanded_url AND $url->display_url) {
1186 $expanded_url = original_url($url->expanded_url);
1188 $oembed_data = oembed_fetch_url($expanded_url);
1190 // Quickfix: Workaround for URL with "[" and "]" in it
1191 if (strpos($expanded_url, "[") OR strpos($expanded_url, "]"))
1192 $expanded_url = $url->url;
1195 $type = $oembed_data->type;
1197 if ($oembed_data->type == "video") {
1198 //$body = str_replace($url->url,
1199 // "[video]".$expanded_url."[/video]", $body);
1200 //$dontincludemedia = true;
1201 $type = $oembed_data->type;
1202 $footerurl = $expanded_url;
1203 $footerlink = "[url=".$expanded_url."]".$expanded_url."[/url]";
1205 $body = str_replace($url->url, $footerlink, $body);
1206 //} elseif (($oembed_data->type == "photo") AND isset($oembed_data->url) AND !$dontincludemedia) {
1207 } elseif (($oembed_data->type == "photo") AND isset($oembed_data->url)) {
1208 $body = str_replace($url->url,
1209 "[url=".$expanded_url."][img]".$oembed_data->url."[/img][/url]",
1211 //$dontincludemedia = true;
1212 } elseif ($oembed_data->type != "link")
1213 $body = str_replace($url->url,
1214 "[url=".$expanded_url."]".$expanded_url."[/url]",
1217 $img_str = fetch_url($expanded_url, true, $redirects, 4);
1219 $tempfile = tempnam(get_temppath(), "cache");
1220 file_put_contents($tempfile, $img_str);
1221 $mime = image_type_to_mime_type(exif_imagetype($tempfile));
1224 if (substr($mime, 0, 6) == "image/") {
1226 $body = str_replace($url->url, "[img]".$expanded_url."[/img]", $body);
1227 //$dontincludemedia = true;
1229 $type = $oembed_data->type;
1230 $footerurl = $expanded_url;
1231 $footerlink = "[url=".$expanded_url."]".$expanded_url."[/url]";
1233 $body = str_replace($url->url, $footerlink, $body);
1239 if ($footerurl != "")
1240 $footer = add_page_info($footerurl, false, $picture);
1242 if (($footerlink != "") AND (trim($footer) != "")) {
1243 $removedlink = trim(str_replace($footerlink, "", $body));
1245 if (($removedlink == "") OR strstr($body, $removedlink))
1246 $body = $removedlink;
1251 if (($footer == "") AND ($picture != ""))
1252 $body .= "\n\n[img]".$picture."[/img]\n";
1253 elseif (($footer == "") AND ($picture == ""))
1254 $body = add_page_info_to_body($body);
1257 return(array("body" => $body, "tags" => ""));
1259 $tags_arr = array();
1261 foreach ($item->entities->hashtags AS $hashtag) {
1262 $url = "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag->text)."]".$hashtag->text."[/url]";
1263 $tags_arr["#".$hashtag->text] = $url;
1264 $body = str_replace("#".$hashtag->text, $url, $body);
1267 foreach ($item->entities->user_mentions AS $mention) {
1268 $url = "@[url=https://twitter.com/".rawurlencode($mention->screen_name)."]".$mention->screen_name."[/url]";
1269 $tags_arr["@".$mention->screen_name] = $url;
1270 $body = str_replace("@".$mention->screen_name, $url, $body);
1273 // it seems as if the entities aren't always covering all mentions. So the rest will be checked here
1274 $tags = get_tags($body);
1277 foreach($tags as $tag) {
1278 if (strstr(trim($tag), " "))
1281 if(strpos($tag,'#') === 0) {
1282 if(strpos($tag,'[url='))
1285 // don't link tags that are already embedded in links
1287 if(preg_match('/\[(.*?)' . preg_quote($tag,'/') . '(.*?)\]/',$body))
1289 if(preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag,'/') . '(.*?)\)/',$body))
1292 $basetag = str_replace('_',' ',substr($tag,1));
1293 $url = '#[url='.$a->get_baseurl().'/search?tag='.rawurlencode($basetag).']'.$basetag.'[/url]';
1294 $body = str_replace($tag,$url,$body);
1295 $tags_arr["#".$basetag] = $url;
1297 } elseif(strpos($tag,'@') === 0) {
1298 if(strpos($tag,'[url='))
1301 $basetag = substr($tag,1);
1302 $url = '@[url=https://twitter.com/'.rawurlencode($basetag).']'.$basetag.'[/url]';
1303 $body = str_replace($tag,$url,$body);
1304 $tags_arr["@".$basetag] = $url;
1310 $tags = implode($tags_arr, ",");
1313 return(array("body" => $body, "tags" => $tags));
1316 function twitter_createpost($a, $uid, $post, $self, $create_user, $only_existing_contact) {
1318 $has_picture = false;
1320 $postarray = array();
1321 $postarray['network'] = NETWORK_TWITTER;
1322 $postarray['gravity'] = 0;
1323 $postarray['uid'] = $uid;
1324 $postarray['wall'] = 0;
1325 $postarray['uri'] = "twitter::".$post->id_str;
1327 $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1328 dbesc($postarray['uri']),
1337 if ($post->in_reply_to_status_id_str != "") {
1339 $parent = "twitter::".$post->in_reply_to_status_id_str;
1341 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1346 $postarray['thr-parent'] = $r[0]["uri"];
1347 $postarray['parent-uri'] = $r[0]["parent-uri"];
1348 $postarray['parent'] = $r[0]["parent"];
1349 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1351 $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1",
1356 $postarray['thr-parent'] = $r[0]['uri'];
1357 $postarray['parent-uri'] = $r[0]['parent-uri'];
1358 $postarray['parent'] = $r[0]['parent'];
1359 $postarray['object-type'] = ACTIVITY_OBJ_COMMENT;
1361 $postarray['thr-parent'] = $postarray['uri'];
1362 $postarray['parent-uri'] = $postarray['uri'];
1363 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1368 $own_id = get_pconfig($uid, 'twitter', 'own_id');
1370 if ($post->user->id_str == $own_id) {
1371 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1375 $contactid = $r[0]["id"];
1377 $postarray['owner-name'] = $r[0]["name"];
1378 $postarray['owner-link'] = $r[0]["url"];
1379 $postarray['owner-avatar'] = $r[0]["photo"];
1383 // Don't create accounts of people who just comment something
1384 $create_user = false;
1386 $postarray['parent-uri'] = $postarray['uri'];
1387 $postarray['object-type'] = ACTIVITY_OBJ_NOTE;
1390 if ($contactid == 0) {
1391 $contactid = twitter_fetch_contact($uid, $post->user, $create_user);
1393 $postarray['owner-name'] = $post->user->name;
1394 $postarray['owner-link'] = "https://twitter.com/".$post->user->screen_name;
1395 $postarray['owner-avatar'] = twitter_fix_avatar($post->user->profile_image_url_https);
1398 if(($contactid == 0) AND !$only_existing_contact)
1399 $contactid = $self['id'];
1400 elseif ($contactid <= 0)
1403 $postarray['contact-id'] = $contactid;
1405 $postarray['verb'] = ACTIVITY_POST;
1406 $postarray['author-name'] = $postarray['owner-name'];
1407 $postarray['author-link'] = $postarray['owner-link'];
1408 $postarray['author-avatar'] = $postarray['owner-avatar'];
1409 $postarray['plink'] = "https://twitter.com/".$post->user->screen_name."/status/".$post->id_str;
1410 $postarray['app'] = strip_tags($post->source);
1412 if ($post->user->protected) {
1413 $postarray['private'] = 1;
1414 $postarray['allow_cid'] = '<' . $self['id'] . '>';
1417 $postarray['body'] = $post->text;
1422 if (is_array($post->entities->media)) {
1423 foreach($post->entities->media AS $media) {
1424 switch($media->type) {
1426 //$postarray['body'] = str_replace($media->url, "\n\n[img]".$media->media_url_https."[/img]\n", $postarray['body']);
1427 //$has_picture = true;
1428 $postarray['body'] = str_replace($media->url, "", $postarray['body']);
1429 $postarray['object-type'] = ACTIVITY_OBJ_IMAGE;
1430 $picture = $media->media_url_https;
1433 $postarray['body'] .= print_r($media, true);
1438 $converted = twitter_expand_entities($a, $postarray['body'], $post, false, $picture);
1439 $postarray['body'] = $converted["body"];
1440 $postarray['tag'] = $converted["tags"];
1442 $postarray['created'] = datetime_convert('UTC','UTC',$post->created_at);
1443 $postarray['edited'] = datetime_convert('UTC','UTC',$post->created_at);
1445 if (is_string($post->place->name))
1446 $postarray["location"] = $post->place->name;
1448 if (is_string($post->place->full_name))
1449 $postarray["location"] = $post->place->full_name;
1451 if (is_array($post->geo->coordinates))
1452 $postarray["coord"] = $post->geo->coordinates[0]." ".$post->geo->coordinates[1];
1454 if (is_array($post->coordinates->coordinates))
1455 $postarray["coord"] = $post->coordinates->coordinates[1]." ".$post->coordinates->coordinates[0];
1457 if (is_object($post->retweeted_status)) {
1459 $postarray['body'] = $post->retweeted_status->text;
1464 if (is_array($post->retweeted_status->entities->media)) {
1465 foreach($post->retweeted_status->entities->media AS $media) {
1466 switch($media->type) {
1468 //$postarray['body'] = str_replace($media->url, "\n\n[img]".$media->media_url_https."[/img]\n", $postarray['body']);
1469 //$has_picture = true;
1470 $postarray['body'] = str_replace($media->url, "", $postarray['body']);
1471 $postarray['object-type'] = ACTIVITY_OBJ_IMAGE;
1472 $picture = $media->media_url_https;
1475 $postarray['body'] .= print_r($media, true);
1480 $converted = twitter_expand_entities($a, $postarray['body'], $post->retweeted_status, false, $picture);
1481 $postarray['body'] = $converted["body"];
1482 $postarray['tag'] = $converted["tags"];
1484 twitter_fetch_contact($uid, $post->retweeted_status->user, false);
1486 // Deactivated at the moment, since there are problems with answers to retweets
1487 if (false AND !intval(get_config('system','wall-to-wall_share'))) {
1488 $postarray['body'] = "[share author='".$post->retweeted_status->user->name.
1489 "' profile='https://twitter.com/".$post->retweeted_status->user->screen_name.
1490 "' avatar='".$post->retweeted_status->user->profile_image_url_https.
1491 "' posted='".datetime_convert('UTC','UTC',$post->retweeted_status->created_at).
1492 "' link='https://twitter.com/".$post->retweeted_status->user->screen_name."/status/".$post->retweeted_status->id_str."']".
1494 $postarray['body'] .= "[/share]";
1496 // Let retweets look like wall-to-wall posts
1497 $postarray['author-name'] = $post->retweeted_status->user->name;
1498 $postarray['author-link'] = "https://twitter.com/".$post->retweeted_status->user->screen_name;
1499 $postarray['author-avatar'] = twitter_fix_avatar($post->retweeted_status->user->profile_image_url_https);
1500 //if (($post->retweeted_status->user->screen_name != "") AND ($post->retweeted_status->id_str != "")) {
1501 // $postarray['plink'] = "https://twitter.com/".$post->retweeted_status->user->screen_name."/status/".$post->retweeted_status->id_str;
1502 // $postarray['uri'] = "twitter::".$post->retweeted_status->id_str;
1510 function twitter_checknotification($a, $uid, $own_id, $top_item, $postarray) {
1512 // this whole function doesn't seem to work. Needs complete check
1514 $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
1522 if (link_compare($user[0]["url"], $postarray['author-link']))
1525 $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1527 dbesc("twitter::".$own_id)
1530 if(!count($own_user))
1533 // Is it me from twitter?
1534 if (link_compare($own_user[0]["url"], $postarray['author-link']))
1537 $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0",
1538 dbesc($postarray['parent-uri']),
1542 if(count($myconv)) {
1544 foreach($myconv as $conv) {
1545 // now if we find a match, it means we're in this conversation
1547 if(!link_compare($conv['author-link'],$user[0]["url"]) AND !link_compare($conv['author-link'],$own_user[0]["url"]))
1550 require_once('include/enotify.php');
1552 $conv_parent = $conv['parent'];
1555 'type' => NOTIFY_COMMENT,
1556 'notify_flags' => $user[0]['notify-flags'],
1557 'language' => $user[0]['language'],
1558 'to_name' => $user[0]['username'],
1559 'to_email' => $user[0]['email'],
1560 'uid' => $user[0]['uid'],
1561 'item' => $postarray,
1562 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($top_item)),
1563 'source_name' => $postarray['author-name'],
1564 'source_link' => $postarray['author-link'],
1565 'source_photo' => $postarray['author-avatar'],
1566 'verb' => ACTIVITY_POST,
1568 'parent' => $conv_parent,
1571 // only send one notification
1577 function twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id) {
1578 logger("twitter_fetchparentposts: Fetching for user ".$uid." and post ".$post->id_str, LOGGER_DEBUG);
1582 while ($post->in_reply_to_status_id_str != "") {
1583 $parameters = array("trim_user" => false, "id" => $post->in_reply_to_status_id_str);
1585 $post = $connection->get('statuses/show', $parameters);
1587 if (!count($post)) {
1588 logger("twitter_fetchparentposts: Can't fetch post ".$parameters->id, LOGGER_DEBUG);
1592 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1593 dbesc("twitter::".$post->id_str),
1603 logger("twitter_fetchparentposts: Fetching ".count($posts)." parents", LOGGER_DEBUG);
1605 $posts = array_reverse($posts);
1607 if (count($posts)) {
1608 foreach ($posts as $post) {
1609 $postarray = twitter_createpost($a, $uid, $post, $self, false, false);
1611 if (trim($postarray['body']) == "")
1614 $item = item_store($postarray);
1615 $postarray["id"] = $item;
1617 logger('twitter_fetchparentpost: User '.$self["nick"].' posted parent timeline item '.$item);
1619 if ($item AND !function_exists("check_item_notification"))
1620 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
1625 function twitter_fetchhometimeline($a, $uid) {
1626 $ckey = get_config('twitter', 'consumerkey');
1627 $csecret = get_config('twitter', 'consumersecret');
1628 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
1629 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
1630 $create_user = get_pconfig($uid, 'twitter', 'create_user');
1631 $mirror_posts = get_pconfig($uid, 'twitter', 'mirror_posts');
1633 logger("twitter_fetchhometimeline: Fetching for user ".$uid, LOGGER_DEBUG);
1635 $application_name = get_config('twitter', 'application_name');
1637 if ($application_name == "")
1638 $application_name = $a->get_hostname();
1640 require_once('library/twitteroauth.php');
1641 require_once('include/items.php');
1643 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
1645 $own_contact = twitter_fetch_own_contact($a, $uid);
1647 $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
1648 intval($own_contact),
1652 $own_id = $r[0]["nick"];
1654 logger("twitter_fetchhometimeline: Own twitter contact not found for user ".$uid, LOGGER_DEBUG);
1658 $r = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
1664 logger("twitter_fetchhometimeline: Own contact not found for user ".$uid, LOGGER_DEBUG);
1668 $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1",
1671 logger("twitter_fetchhometimeline: Own user not found for user ".$uid, LOGGER_DEBUG);
1675 $parameters = array("exclude_replies" => false, "trim_user" => false, "contributor_details" => true, "include_rts" => true);
1676 //$parameters["count"] = 200;
1679 // Fetching timeline
1680 $lastid = get_pconfig($uid, 'twitter', 'lasthometimelineid');
1682 $first_time = ($lastid == "");
1685 $parameters["since_id"] = $lastid;
1687 $items = $connection->get('statuses/home_timeline', $parameters);
1689 if (!is_array($items)) {
1690 logger("twitter_fetchhometimeline: Error fetching home timeline: ".print_r($items, true), LOGGER_DEBUG);
1694 $posts = array_reverse($items);
1696 logger("twitter_fetchhometimeline: Fetching timeline for user ".$uid." ".sizeof($posts)." items", LOGGER_DEBUG);
1698 if (count($posts)) {
1699 foreach ($posts as $post) {
1700 if ($post->id_str > $lastid)
1701 $lastid = $post->id_str;
1706 if (stristr($post->source, $application_name) && $post->user->screen_name == $own_id) {
1707 logger("twitter_fetchhometimeline: Skip previously sended post", LOGGER_DEBUG);
1711 if ($mirror_posts && $post->user->screen_name == $own_id && $post->in_reply_to_status_id_str == "") {
1712 logger("twitter_fetchhometimeline: Skip post that will be mirrored", LOGGER_DEBUG);
1716 if ($post->in_reply_to_status_id_str != "")
1717 twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
1719 $postarray = twitter_createpost($a, $uid, $post, $self, $create_user, true);
1721 if (trim($postarray['body']) == "")
1724 $item = item_store($postarray);
1725 $postarray["id"] = $item;
1727 logger('twitter_fetchhometimeline: User '.$self["nick"].' posted home timeline item '.$item);
1729 if ($item AND !function_exists("check_item_notification"))
1730 twitter_checknotification($a, $uid, $own_id, $item, $postarray);
1734 set_pconfig($uid, 'twitter', 'lasthometimelineid', $lastid);
1736 // Fetching mentions
1737 $lastid = get_pconfig($uid, 'twitter', 'lastmentionid');
1739 $first_time = ($lastid == "");
1742 $parameters["since_id"] = $lastid;
1744 $items = $connection->get('statuses/mentions_timeline', $parameters);
1746 if (!is_array($items)) {
1747 logger("twitter_fetchhometimeline: Error fetching mentions: ".print_r($items, true), LOGGER_DEBUG);
1751 $posts = array_reverse($items);
1753 logger("twitter_fetchhometimeline: Fetching mentions for user ".$uid." ".sizeof($posts)." items", LOGGER_DEBUG);
1755 if (count($posts)) {
1756 foreach ($posts as $post) {
1757 if ($post->id_str > $lastid)
1758 $lastid = $post->id_str;
1763 if ($post->in_reply_to_status_id_str != "")
1764 twitter_fetchparentposts($a, $uid, $post, $connection, $self, $own_id);
1766 $postarray = twitter_createpost($a, $uid, $post, $self, false, false);
1768 if (trim($postarray['body']) == "")
1771 $item = item_store($postarray);
1772 $postarray["id"] = $item;
1774 if ($item AND function_exists("check_item_notification"))
1775 check_item_notification($item, $uid, NOTIFY_TAGSELF);
1777 if (!isset($postarray["parent"]) OR ($postarray["parent"] == 0))
1778 $postarray["parent"] = $item;
1780 logger('twitter_fetchhometimeline: User '.$self["nick"].' posted mention timeline item '.$item);
1783 $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
1784 dbesc($postarray['uri']),
1788 $item = $r[0]['id'];
1789 $parent_id = $r[0]['parent'];
1792 $parent_id = $postarray['parent'];
1794 if (($item != 0) AND !function_exists("check_item_notification")) {
1795 require_once('include/enotify.php');
1797 'type' => NOTIFY_TAGSELF,
1798 'notify_flags' => $u[0]['notify-flags'],
1799 'language' => $u[0]['language'],
1800 'to_name' => $u[0]['username'],
1801 'to_email' => $u[0]['email'],
1802 'uid' => $u[0]['uid'],
1803 'item' => $postarray,
1804 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($item)),
1805 'source_name' => $postarray['author-name'],
1806 'source_link' => $postarray['author-link'],
1807 'source_photo' => $postarray['author-avatar'],
1808 'verb' => ACTIVITY_TAG,
1810 'parent' => $parent_id
1816 set_pconfig($uid, 'twitter', 'lastmentionid', $lastid);
1819 function twitter_fetch_own_contact($a, $uid) {
1820 $ckey = get_config('twitter', 'consumerkey');
1821 $csecret = get_config('twitter', 'consumersecret');
1822 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
1823 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
1825 $own_id = get_pconfig($uid, 'twitter', 'own_id');
1829 if ($own_id == "") {
1830 require_once('library/twitteroauth.php');
1832 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
1834 // Fetching user data
1835 $user = $connection->get('account/verify_credentials');
1837 set_pconfig($uid, 'twitter', 'own_id', $user->id_str);
1839 $contact_id = twitter_fetch_contact($uid, $user, true);
1842 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1",
1843 intval($uid), dbesc("twitter::".$own_id));
1845 $contact_id = $r[0]["id"];
1847 del_pconfig($uid, 'twitter', 'own_id');
1851 return($contact_id);
1854 function twitter_is_retweet($a, $uid, $body) {
1855 $body = trim($body);
1857 // Skip if it isn't a pure repeated messages
1858 // Does it start with a share?
1859 if (strpos($body, "[share") > 0)
1862 // Does it end with a share?
1863 if (strlen($body) > (strrpos($body, "[/share]") + 8))
1866 $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
1867 // Skip if there is no shared message in there
1868 if ($body == $attributes)
1872 preg_match("/link='(.*?)'/ism", $attributes, $matches);
1873 if ($matches[1] != "")
1874 $link = $matches[1];
1876 preg_match('/link="(.*?)"/ism', $attributes, $matches);
1877 if ($matches[1] != "")
1878 $link = $matches[1];
1880 $id = preg_replace("=https?://twitter.com/(.*)/status/(.*)=ism", "$2", $link);
1884 logger('twitter_is_retweet: Retweeting id '.$id.' for user '.$uid, LOGGER_DEBUG);
1886 $ckey = get_config('twitter', 'consumerkey');
1887 $csecret = get_config('twitter', 'consumersecret');
1888 $otoken = get_pconfig($uid, 'twitter', 'oauthtoken');
1889 $osecret = get_pconfig($uid, 'twitter', 'oauthsecret');
1891 require_once('library/twitteroauth.php');
1892 $connection = new TwitterOAuth($ckey,$csecret,$otoken,$osecret);
1894 $result = $connection->post('statuses/retweet/'.$id);
1896 logger('twitter_is_retweet: result '.print_r($result, true), LOGGER_DEBUG);
1898 return(!isset($result->errors));