forked from GNUsocial/gnu-social
Merge branch 'twitter-oauth' into 0.8.x
Conflicts: scripts/getvaliddaemons.php
This commit is contained in:
commit
17dcf1c317
222
actions/twitterauthorization.php
Normal file
222
actions/twitterauthorization.php
Normal file
@ -0,0 +1,222 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Class for doing OAuth authentication against Twitter
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Twitter
|
||||
* @package Laconica
|
||||
* @author Zach Copely <zach@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for doing OAuth authentication against Twitter
|
||||
*
|
||||
* Peforms the OAuth "dance" between Laconica and Twitter -- requests a token,
|
||||
* authorizes it, and exchanges it for an access token. It also creates a link
|
||||
* (Foreign_link) between the Laconica user and Twitter user and stores the
|
||||
* access token and secret in the link.
|
||||
*
|
||||
* @category Twitter
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
*/
|
||||
class TwitterauthorizationAction extends Action
|
||||
{
|
||||
/**
|
||||
* Initialize class members. Looks for 'oauth_token' parameter.
|
||||
*
|
||||
* @param array $args misc. arguments
|
||||
*
|
||||
* @return boolean true
|
||||
*/
|
||||
function prepare($args)
|
||||
{
|
||||
parent::prepare($args);
|
||||
|
||||
$this->oauth_token = $this->arg('oauth_token');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler method
|
||||
*
|
||||
* @param array $args is ignored since it's now passed in in prepare()
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function handle($args)
|
||||
{
|
||||
parent::handle($args);
|
||||
|
||||
if (!common_logged_in()) {
|
||||
$this->clientError(_('Not logged in.'), 403);
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
|
||||
|
||||
// If there's already a foreign link record, it means we already
|
||||
// have an access token, and this is unecessary. So go back.
|
||||
|
||||
if (isset($flink)) {
|
||||
common_redirect(common_local_url('twittersettings'));
|
||||
}
|
||||
|
||||
// $this->oauth_token is only populated once Twitter authorizes our
|
||||
// request token. If it's empty we're at the beginning of the auth
|
||||
// process
|
||||
|
||||
if (empty($this->oauth_token)) {
|
||||
$this->authorizeRequestToken();
|
||||
} else {
|
||||
$this->saveAccessToken();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks Twitter for a request token, and then redirects to Twitter
|
||||
* to authorize it.
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function authorizeRequestToken()
|
||||
{
|
||||
try {
|
||||
|
||||
// Get a new request token and authorize it
|
||||
|
||||
$client = new TwitterOAuthClient();
|
||||
$req_tok =
|
||||
$client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
|
||||
|
||||
// Sock the request token away in the session temporarily
|
||||
|
||||
$_SESSION['twitter_request_token'] = $req_tok->key;
|
||||
$_SESSION['twitter_request_token_secret'] = $req_tok->secret;
|
||||
|
||||
$auth_link = $client->getAuthorizeLink($req_tok);
|
||||
|
||||
} catch (TwitterOAuthClientException $e) {
|
||||
$msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
|
||||
$e->getCode(), $e->getMessage());
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
common_redirect($auth_link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Twitter returns an authorized request token. Exchanges
|
||||
* it for an access token and stores it.
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function saveAccessToken()
|
||||
{
|
||||
|
||||
// Check to make sure Twitter returned the same request
|
||||
// token we sent them
|
||||
|
||||
if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
|
||||
$_SESSION['twitter_request_token_secret']);
|
||||
|
||||
// Exchange the request token for an access token
|
||||
|
||||
$atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
|
||||
|
||||
// Test the access token and get the user's Twitter info
|
||||
|
||||
$client = new TwitterOAuthClient($atok->key, $atok->secret);
|
||||
$twitter_user = $client->verifyCredentials();
|
||||
|
||||
} catch (OAuthClientException $e) {
|
||||
$msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
|
||||
$e->getCode(), $e->getMessage());
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
// Save the access token and Twitter user info
|
||||
|
||||
$this->saveForeignLink($atok, $twitter_user);
|
||||
|
||||
// Clean up the the mess we made in the session
|
||||
|
||||
unset($_SESSION['twitter_request_token']);
|
||||
unset($_SESSION['twitter_request_token_secret']);
|
||||
|
||||
common_redirect(common_local_url('twittersettings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a Foreign_link between Twitter user and local user,
|
||||
* which includes the access token and secret.
|
||||
*
|
||||
* @param OAuthToken $access_token the access token to save
|
||||
* @param mixed $twitter_user twitter API user object
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function saveForeignLink($access_token, $twitter_user)
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
$flink = new Foreign_link();
|
||||
|
||||
$flink->user_id = $user->id;
|
||||
$flink->foreign_id = $twitter_user->id;
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
|
||||
$creds = TwitterOAuthClient::packToken($access_token);
|
||||
|
||||
$flink->credentials = $creds;
|
||||
$flink->created = common_sql_now();
|
||||
|
||||
// Defaults: noticesync on, everything else off
|
||||
|
||||
$flink->set_flags(true, false, false, false);
|
||||
|
||||
$flink_id = $flink->insert();
|
||||
|
||||
if (empty($flink_id)) {
|
||||
common_log_db_error($flink, 'INSERT', __FILE__);
|
||||
$this->serverError(_('Couldn\'t link your Twitter account.'));
|
||||
}
|
||||
|
||||
save_twitter_user($twitter_user->id, $twitter_user->screen_name);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,8 +34,6 @@ if (!defined('LACONICA')) {
|
||||
require_once INSTALLDIR.'/lib/connectsettingsaction.php';
|
||||
require_once INSTALLDIR.'/lib/twitter.php';
|
||||
|
||||
define('SUBSCRIPTIONS', 80);
|
||||
|
||||
/**
|
||||
* Settings for Twitter integration
|
||||
*
|
||||
@ -69,9 +67,8 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
|
||||
function getInstructions()
|
||||
{
|
||||
return _('Add your Twitter account to automatically send '.
|
||||
' your notices to Twitter, ' .
|
||||
'and subscribe to Twitter friends already here.');
|
||||
return _('Connect your Twitter account to share your updates ' .
|
||||
'with your Twitter friends and vice-versa.');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,7 +96,7 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
|
||||
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
|
||||
|
||||
if ($flink) {
|
||||
if (!empty($flink)) {
|
||||
$fuser = $flink->getForeignUser();
|
||||
}
|
||||
|
||||
@ -108,192 +105,86 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
'class' => 'form_settings',
|
||||
'action' =>
|
||||
common_local_url('twittersettings')));
|
||||
$this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
|
||||
$this->element('legend', null, _('Twitter Account'));
|
||||
|
||||
$this->hidden('token', common_session_token());
|
||||
if ($fuser) {
|
||||
|
||||
$this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
|
||||
|
||||
if (empty($fuser)) {
|
||||
$this->elementStart('ul', 'form_data');
|
||||
$this->elementStart('li', array('id' => 'settings_twitter_remove'));
|
||||
$this->element('span', 'twitter_user', $fuser->nickname);
|
||||
$this->element('a', array('href' => $fuser->uri), $fuser->uri);
|
||||
$this->element('p', 'form_note',
|
||||
_('Current verified Twitter account.'));
|
||||
$this->hidden('flink_foreign_id', $flink->foreign_id);
|
||||
$this->elementStart('li', array('id' => 'settings_twitter_login_button'));
|
||||
$this->element('a', array('href' => common_local_url('twitterauthorization')),
|
||||
'Connect my Twitter account');
|
||||
$this->elementEnd('li');
|
||||
$this->elementEnd('ul');
|
||||
$this->submit('remove', _('Remove'));
|
||||
|
||||
$this->elementEnd('fieldset');
|
||||
} else {
|
||||
$this->element('legend', null, _('Twitter account'));
|
||||
$this->elementStart('p', array('id' => 'form_confirmed'));
|
||||
$this->element('a', array('href' => $fuser->uri), $fuser->nickname);
|
||||
$this->elementEnd('p');
|
||||
$this->element('p', 'form_note',
|
||||
_('Connected Twitter account'));
|
||||
|
||||
$this->submit('remove', _('Remove'));
|
||||
|
||||
$this->elementEnd('fieldset');
|
||||
|
||||
$this->elementStart('fieldset', array('id' => 'settings_twitter_preferences'));
|
||||
|
||||
$this->element('legend', null, _('Preferences'));
|
||||
$this->elementStart('ul', 'form_data');
|
||||
$this->elementStart('li', array('id' => 'settings_twitter_login'));
|
||||
$this->input('twitter_username', _('Twitter user name'),
|
||||
($this->arg('twitter_username')) ?
|
||||
$this->arg('twitter_username') :
|
||||
$profile->nickname,
|
||||
_('No spaces, please.')); // hey, it's what Twitter says
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('noticesend',
|
||||
_('Automatically send my notices to Twitter.'),
|
||||
($flink) ?
|
||||
($flink->noticesync & FOREIGN_NOTICE_SEND) :
|
||||
true);
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li');
|
||||
$this->password('twitter_password', _('Twitter password'));
|
||||
$this->elementend('li');
|
||||
$this->elementEnd('ul');
|
||||
}
|
||||
$this->elementEnd('fieldset');
|
||||
|
||||
$this->elementStart('fieldset',
|
||||
array('id' => 'settings_twitter_preferences'));
|
||||
$this->element('legend', null, _('Preferences'));
|
||||
|
||||
$this->elementStart('ul', 'form_data');
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('noticesend',
|
||||
_('Automatically send my notices to Twitter.'),
|
||||
($flink) ?
|
||||
($flink->noticesync & FOREIGN_NOTICE_SEND) :
|
||||
true);
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('replysync',
|
||||
_('Send local "@" replies to Twitter.'),
|
||||
($flink) ?
|
||||
($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
|
||||
true);
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('friendsync',
|
||||
_('Subscribe to my Twitter friends here.'),
|
||||
($flink) ?
|
||||
($flink->friendsync & FOREIGN_FRIEND_RECV) :
|
||||
false);
|
||||
$this->elementEnd('li');
|
||||
|
||||
if (common_config('twitterbridge','enabled')) {
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('noticerecv',
|
||||
_('Import my Friends Timeline.'),
|
||||
$this->checkbox('replysync',
|
||||
_('Send local "@" replies to Twitter.'),
|
||||
($flink) ?
|
||||
($flink->noticesync & FOREIGN_NOTICE_RECV) :
|
||||
($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
|
||||
true);
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('friendsync',
|
||||
_('Subscribe to my Twitter friends here.'),
|
||||
($flink) ?
|
||||
($flink->friendsync & FOREIGN_FRIEND_RECV) :
|
||||
false);
|
||||
$this->elementEnd('li');
|
||||
} else {
|
||||
// preserve setting even if bidrection bridge toggled off
|
||||
if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
|
||||
$this->hidden('noticerecv', true, 'noticerecv');
|
||||
}
|
||||
}
|
||||
|
||||
$this->elementEnd('ul');
|
||||
|
||||
if ($flink) {
|
||||
$this->submit('save', _('Save'));
|
||||
} else {
|
||||
$this->submit('add', _('Add'));
|
||||
}
|
||||
$this->elementEnd('fieldset');
|
||||
|
||||
$this->showTwitterSubscriptions();
|
||||
|
||||
$this->elementEnd('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets some of the user's Twitter friends
|
||||
*
|
||||
* Gets the number of Twitter friends that are on this
|
||||
* instance of Laconica.
|
||||
*
|
||||
* @return array array of User objects
|
||||
*/
|
||||
|
||||
function subscribedTwitterUsers()
|
||||
{
|
||||
|
||||
$current_user = common_current_user();
|
||||
|
||||
$qry = 'SELECT "user".* ' .
|
||||
'FROM subscription ' .
|
||||
'JOIN "user" ON subscription.subscribed = "user".id ' .
|
||||
'JOIN foreign_link ON foreign_link.user_id = "user".id ' .
|
||||
'WHERE subscriber = %d ' .
|
||||
'ORDER BY "user".nickname';
|
||||
|
||||
$user = new User();
|
||||
|
||||
$user->query(sprintf($qry, $current_user->id));
|
||||
|
||||
$users = array();
|
||||
|
||||
while ($user->fetch()) {
|
||||
|
||||
// Don't include the user's own self-subscription
|
||||
if ($user->id != $current_user->id) {
|
||||
$users[] = clone($user);
|
||||
}
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show user's Twitter friends
|
||||
*
|
||||
* Gets the number of Twitter friends that are on this
|
||||
* instance of Laconica, and shows their mini-avatars.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showTwitterSubscriptions()
|
||||
{
|
||||
|
||||
$friends = $this->subscribedTwitterUsers();
|
||||
|
||||
$friends_count = count($friends);
|
||||
|
||||
if ($friends_count > 0) {
|
||||
$this->elementStart('div', array('id' => 'entity_subscriptions',
|
||||
'class' => 'section'));
|
||||
$this->element('h2', null, _('Twitter Friends'));
|
||||
$this->elementStart('ul', 'entities users xoxo');
|
||||
|
||||
for ($i = 0; $i < min($friends_count, SUBSCRIPTIONS); $i++) {
|
||||
|
||||
$other = Profile::staticGet($friends[$i]->id);
|
||||
|
||||
if (!$other) {
|
||||
common_log_db_error($subs, 'SELECT', __FILE__);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->elementStart('li', 'vcard');
|
||||
$this->elementStart('a', array('title' => ($other->fullname) ?
|
||||
$other->fullname :
|
||||
$other->nickname,
|
||||
'href' => $other->profileurl,
|
||||
'class' => 'url'));
|
||||
|
||||
$avatar = $other->getAvatar(AVATAR_MINI_SIZE);
|
||||
|
||||
$avatar_url = ($avatar) ?
|
||||
$avatar->displayUrl() :
|
||||
Avatar::defaultImage(AVATAR_MINI_SIZE);
|
||||
|
||||
$this->element('img', array('src' => $avatar_url,
|
||||
'width' => AVATAR_MINI_SIZE,
|
||||
'height' => AVATAR_MINI_SIZE,
|
||||
'class' => 'avatar photo',
|
||||
'alt' => ($other->fullname) ?
|
||||
$other->fullname :
|
||||
$other->nickname));
|
||||
|
||||
$this->element('span', 'fn nickname', $other->nickname);
|
||||
$this->elementEnd('a');
|
||||
if (common_config('twitterbridge','enabled')) {
|
||||
$this->elementStart('li');
|
||||
$this->checkbox('noticerecv',
|
||||
_('Import my Friends Timeline.'),
|
||||
($flink) ?
|
||||
($flink->noticesync & FOREIGN_NOTICE_RECV) :
|
||||
false);
|
||||
$this->elementEnd('li');
|
||||
|
||||
// preserve setting even if bidrection bridge toggled off
|
||||
|
||||
if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
|
||||
$this->hidden('noticerecv', true, 'noticerecv');
|
||||
}
|
||||
}
|
||||
|
||||
$this->elementEnd('ul');
|
||||
$this->elementEnd('div');
|
||||
|
||||
if ($flink) {
|
||||
$this->submit('save', _('Save'));
|
||||
} else {
|
||||
$this->submit('add', _('Add'));
|
||||
}
|
||||
|
||||
$this->elementEnd('fieldset');
|
||||
}
|
||||
|
||||
$this->elementEnd('form');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -309,7 +200,6 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
|
||||
function handlePost()
|
||||
{
|
||||
|
||||
// CSRF protection
|
||||
$token = $this->trimmed('token');
|
||||
if (!$token || $token != common_session_token()) {
|
||||
@ -320,8 +210,6 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
|
||||
if ($this->arg('save')) {
|
||||
$this->savePreferences();
|
||||
} else if ($this->arg('add')) {
|
||||
$this->addTwitterAccount();
|
||||
} else if ($this->arg('remove')) {
|
||||
$this->removeTwitterAccount();
|
||||
} else {
|
||||
@ -329,82 +217,6 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a Twitter account with the user's account
|
||||
*
|
||||
* Validates post input; verifies it against Twitter; and if
|
||||
* successful stores in the database.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function addTwitterAccount()
|
||||
{
|
||||
$screen_name = $this->trimmed('twitter_username');
|
||||
$password = $this->trimmed('twitter_password');
|
||||
$noticesend = $this->boolean('noticesend');
|
||||
$noticerecv = $this->boolean('noticerecv');
|
||||
$replysync = $this->boolean('replysync');
|
||||
$friendsync = $this->boolean('friendsync');
|
||||
|
||||
if (!Validate::string($screen_name,
|
||||
array('min_length' => 1,
|
||||
'max_length' => 15,
|
||||
'format' => VALIDATE_NUM.VALIDATE_ALPHA.'_'))) {
|
||||
$this->showForm(_('Username must have only numbers, '.
|
||||
'upper- and lowercase letters, '.
|
||||
'and underscore (_). 15 chars max.'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->verifyCredentials($screen_name, $password)) {
|
||||
$this->showForm(_('Could not verify your Twitter credentials!'));
|
||||
return;
|
||||
}
|
||||
|
||||
$twit_user = twitter_user_info($screen_name, $password);
|
||||
|
||||
if (!$twit_user) {
|
||||
$this->showForm(sprintf(_('Unable to retrieve account information '.
|
||||
'For "%s" from Twitter.'),
|
||||
$screen_name));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!save_twitter_user($twit_user->id, $screen_name)) {
|
||||
$this->showForm(_('Unable to save your Twitter settings!'));
|
||||
return;
|
||||
}
|
||||
|
||||
$user = common_current_user();
|
||||
|
||||
$flink = new Foreign_link();
|
||||
|
||||
$flink->user_id = $user->id;
|
||||
$flink->foreign_id = $twit_user->id;
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
$flink->credentials = $password;
|
||||
$flink->created = common_sql_now();
|
||||
|
||||
$flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
|
||||
|
||||
$flink_id = $flink->insert();
|
||||
|
||||
if (!$flink_id) {
|
||||
common_log_db_error($flink, 'INSERT', __FILE__);
|
||||
$this->showForm(_('Unable to save your Twitter settings!'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($friendsync) {
|
||||
save_twitter_friends($user, $twit_user->id, $screen_name, $password);
|
||||
$flink->last_friendsync = common_sql_now();
|
||||
$flink->update();
|
||||
}
|
||||
|
||||
$this->showForm(_('Twitter settings saved.'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disassociate an existing Twitter account from this account
|
||||
*
|
||||
@ -414,20 +226,11 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
function removeTwitterAccount()
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
$flink = Foreign_link::getByUserID($user->id, 1);
|
||||
|
||||
$flink_foreign_id = $this->arg('flink_foreign_id');
|
||||
|
||||
// Maybe an old tab open...?
|
||||
if ($flink->foreign_id != $flink_foreign_id) {
|
||||
$this->showForm(_('That is not your Twitter account.'));
|
||||
return;
|
||||
}
|
||||
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
|
||||
|
||||
$result = $flink->delete();
|
||||
|
||||
if (!$result) {
|
||||
if (empty($result)) {
|
||||
common_log_db_error($flink, 'DELETE', __FILE__);
|
||||
$this->serverError(_('Couldn\'t remove Twitter user.'));
|
||||
return;
|
||||
@ -450,32 +253,16 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
$replysync = $this->boolean('replysync');
|
||||
|
||||
$user = common_current_user();
|
||||
$flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
|
||||
|
||||
$flink = Foreign_link::getByUserID($user->id, 1);
|
||||
|
||||
if (!$flink) {
|
||||
if (empty($flink)) {
|
||||
common_log_db_error($flink, 'SELECT', __FILE__);
|
||||
$this->showForm(_('Couldn\'t save Twitter preferences.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$twitter_id = $flink->foreign_id;
|
||||
$password = $flink->credentials;
|
||||
|
||||
$fuser = $flink->getForeignUser();
|
||||
|
||||
if (!$fuser) {
|
||||
common_log_db_error($fuser, 'SELECT', __FILE__);
|
||||
$this->showForm(_('Couldn\'t save Twitter preferences.'));
|
||||
return;
|
||||
}
|
||||
|
||||
$screen_name = $fuser->nickname;
|
||||
|
||||
$original = clone($flink);
|
||||
|
||||
$flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
|
||||
|
||||
$result = $flink->update($original);
|
||||
|
||||
if ($result === false) {
|
||||
@ -484,45 +271,7 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
return;
|
||||
}
|
||||
|
||||
if ($friendsync) {
|
||||
save_twitter_friends($user, $flink->foreign_id, $screen_name, $password);
|
||||
}
|
||||
|
||||
$this->showForm(_('Twitter preferences saved.'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a username and password against Twitter's API
|
||||
*
|
||||
* @param string $screen_name Twitter user name
|
||||
* @param string $password Twitter password
|
||||
*
|
||||
* @return boolean success flag
|
||||
*/
|
||||
|
||||
function verifyCredentials($screen_name, $password)
|
||||
{
|
||||
$uri = 'http://twitter.com/account/verify_credentials.json';
|
||||
|
||||
$data = get_twitter_data($uri, $screen_name, $password);
|
||||
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = json_decode($data);
|
||||
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$twitter_id = $user->id;
|
||||
|
||||
if ($twitter_id) {
|
||||
return $twitter_id;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -182,6 +182,10 @@ $config['sphinx']['port'] = 3312;
|
||||
//
|
||||
// $config['twitterbridge']['enabled'] = true;
|
||||
|
||||
// Twitter OAuth settings
|
||||
// $config['twitter']['consumer_key'] = 'YOURKEY';
|
||||
// $config['twitter']['consumer_secret'] = 'YOURSECRET';
|
||||
|
||||
// Edit throttling. Off by default. If turned on, you can only post 20 notices
|
||||
// every 10 minutes. Admins may want to play with the settings to minimize inconvenience for
|
||||
// real users without getting uncontrollable floods from spammers or runaway bots.
|
||||
|
@ -194,6 +194,9 @@ $config =
|
||||
'integration' =>
|
||||
array('source' => 'Laconica', # source attribute for Twitter
|
||||
'taguri' => $_server.',2009'), # base for tag URIs
|
||||
'twitter' =>
|
||||
array('consumer_key' => null,
|
||||
'consumer_secret' => null),
|
||||
'memcached' =>
|
||||
array('enabled' => false,
|
||||
'server' => 'localhost',
|
||||
|
19
lib/mail.php
19
lib/mail.php
@ -657,13 +657,14 @@ function mail_twitter_bridge_removed($user)
|
||||
|
||||
$subject = sprintf(_('Your Twitter bridge has been disabled.'));
|
||||
|
||||
$body = sprintf(_("Hi, %1\$s. We're sorry to inform you that your " .
|
||||
'link to Twitter has been disabled. Your Twitter credentials ' .
|
||||
'have either changed (did you recently change your Twitter ' .
|
||||
'password?) or you have otherwise revoked our access to your ' .
|
||||
"Twitter account.\n\n" .
|
||||
'You can re-enable your Twitter bridge by visiting your ' .
|
||||
"Twitter settings page:\n\n\t%2\$s\n\n" .
|
||||
$site_name = common_config('site', 'name');
|
||||
|
||||
$body = sprintf(_('Hi, %1$s. We\'re sorry to inform you that your ' .
|
||||
'link to Twitter has been disabled. We no longer seem to have ' .
|
||||
'permission to update your Twitter status. (Did you revoke ' .
|
||||
'%3$s\'s access?)' . "\n\n" .
|
||||
'You can re-enable your Twitter bridge by visiting your ' .
|
||||
"Twitter settings page:\n\n\t%2\$s\n\n" .
|
||||
"Regards,\n%3\$s\n"),
|
||||
$profile->getBestName(),
|
||||
common_local_url('twittersettings'),
|
||||
@ -691,11 +692,11 @@ function mail_facebook_app_removed($user)
|
||||
$site_name = common_config('site', 'name');
|
||||
|
||||
$subject = sprintf(
|
||||
_('Your %1\$s Facebook application access has been disabled.',
|
||||
_('Your %1$s Facebook application access has been disabled.',
|
||||
$site_name));
|
||||
|
||||
$body = sprintf(_("Hi, %1\$s. We're sorry to inform you that we are " .
|
||||
'unable to update your Facebook status from %2\$s, and have disabled ' .
|
||||
'unable to update your Facebook status from %2$s, and have disabled ' .
|
||||
'the Facebook application for your account. This may be because ' .
|
||||
'you have removed the Facebook application\'s authorization, or ' .
|
||||
'have deleted your Facebook account. You can re-enable the ' .
|
||||
|
225
lib/oauthclient.php
Normal file
225
lib/oauthclient.php
Normal file
@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Base class for doing OAuth calls as a consumer
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Action
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @copyright 2008 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once 'OAuth.php';
|
||||
|
||||
/**
|
||||
* Exception wrapper for cURL errors
|
||||
*
|
||||
* @category Integration
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
*/
|
||||
class OAuthClientCurlException extends Exception
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for doing OAuth calls as a consumer
|
||||
*
|
||||
* @category Integration
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
*/
|
||||
class OAuthClient
|
||||
{
|
||||
var $consumer;
|
||||
var $token;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Can be initialized with just consumer key and secret for requesting new
|
||||
* tokens or with additional request token or access token
|
||||
*
|
||||
* @param string $consumer_key consumer key
|
||||
* @param string $consumer_secret consumer secret
|
||||
* @param string $oauth_token user's token
|
||||
* @param string $oauth_token_secret user's secret
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function __construct($consumer_key, $consumer_secret,
|
||||
$oauth_token = null, $oauth_token_secret = null)
|
||||
{
|
||||
$this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
|
||||
$this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
|
||||
$this->token = null;
|
||||
|
||||
if (isset($oauth_token) && isset($oauth_token_secret)) {
|
||||
$this->token = new OAuthToken($oauth_token, $oauth_token_secret);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a request token from the given url
|
||||
*
|
||||
* @param string $url OAuth endpoint for grabbing request tokens
|
||||
*
|
||||
* @return OAuthToken $token the request token
|
||||
*/
|
||||
function getRequestToken($url)
|
||||
{
|
||||
$response = $this->oAuthGet($url);
|
||||
parse_str($response);
|
||||
$token = new OAuthToken($oauth_token, $oauth_token_secret);
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a link that can be redirected to in order to
|
||||
* authorize a request token.
|
||||
*
|
||||
* @param string $url endpoint for authorizing request tokens
|
||||
* @param OAuthToken $request_token the request token to be authorized
|
||||
* @param string $oauth_callback optional callback url
|
||||
*
|
||||
* @return string $authorize_url the url to redirect to
|
||||
*/
|
||||
function getAuthorizeLink($url, $request_token, $oauth_callback = null)
|
||||
{
|
||||
$authorize_url = $url . '?oauth_token=' .
|
||||
$request_token->key;
|
||||
|
||||
if (isset($oauth_callback)) {
|
||||
$authorize_url .= '&oauth_callback=' . urlencode($oauth_callback);
|
||||
}
|
||||
|
||||
return $authorize_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an access token
|
||||
*
|
||||
* @param string $url OAuth endpoint for exchanging authorized request tokens
|
||||
* for access tokens
|
||||
*
|
||||
* @return OAuthToken $token the access token
|
||||
*/
|
||||
function getAccessToken($url)
|
||||
{
|
||||
$response = $this->oAuthPost($url);
|
||||
parse_str($response);
|
||||
$token = new OAuthToken($oauth_token, $oauth_token_secret);
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use HTTP GET to make a signed OAuth request
|
||||
*
|
||||
* @param string $url OAuth endpoint
|
||||
*
|
||||
* @return mixed the request
|
||||
*/
|
||||
function oAuthGet($url)
|
||||
{
|
||||
$request = OAuthRequest::from_consumer_and_token($this->consumer,
|
||||
$this->token, 'GET', $url, null);
|
||||
$request->sign_request($this->sha1_method,
|
||||
$this->consumer, $this->token);
|
||||
|
||||
return $this->httpRequest($request->to_url());
|
||||
}
|
||||
|
||||
/**
|
||||
* Use HTTP POST to make a signed OAuth request
|
||||
*
|
||||
* @param string $url OAuth endpoint
|
||||
* @param array $params additional post parameters
|
||||
*
|
||||
* @return mixed the request
|
||||
*/
|
||||
function oAuthPost($url, $params = null)
|
||||
{
|
||||
$request = OAuthRequest::from_consumer_and_token($this->consumer,
|
||||
$this->token, 'POST', $url, $params);
|
||||
$request->sign_request($this->sha1_method,
|
||||
$this->consumer, $this->token);
|
||||
|
||||
return $this->httpRequest($request->get_normalized_http_url(),
|
||||
$request->to_postdata());
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a HTTP request using cURL.
|
||||
*
|
||||
* @param string $url Where to make the
|
||||
* @param array $params post parameters
|
||||
*
|
||||
* @return mixed the request
|
||||
*/
|
||||
function httpRequest($url, $params = null)
|
||||
{
|
||||
$options = array(
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_USERAGENT => 'Laconica',
|
||||
CURLOPT_CONNECTTIMEOUT => 120,
|
||||
CURLOPT_TIMEOUT => 120,
|
||||
CURLOPT_HTTPAUTH => CURLAUTH_ANY,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
|
||||
// Twitter is strict about accepting invalid "Expect" headers
|
||||
|
||||
CURLOPT_HTTPHEADER => array('Expect:')
|
||||
);
|
||||
|
||||
if (isset($params)) {
|
||||
$options[CURLOPT_POST] = true;
|
||||
$options[CURLOPT_POSTFIELDS] = $params;
|
||||
}
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, $options);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if ($response === false) {
|
||||
$msg = curl_error($ch);
|
||||
$code = curl_errno($ch);
|
||||
throw new OAuthClientCurlException($msg, $code);
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
}
|
229
lib/parallelizingdaemon.php
Normal file
229
lib/parallelizingdaemon.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Base class for making daemons that can do several tasks in parallel.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Daemon
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @copyright 2009 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
declare(ticks = 1);
|
||||
|
||||
/**
|
||||
* Daemon able to spawn multiple child processes to do work in parallel
|
||||
*
|
||||
* @category Daemon
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
class ParallelizingDaemon extends Daemon
|
||||
{
|
||||
private $_children = array();
|
||||
private $_interval = 0; // seconds
|
||||
private $_max_children = 0; // maximum number of children
|
||||
private $_debug = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $id the name/id of this daemon
|
||||
* @param int $interval sleep this long before doing everything again
|
||||
* @param int $max_children maximum number of child processes at a time
|
||||
* @param boolean $debug debug output flag
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
**/
|
||||
|
||||
function __construct($id = null, $interval = 60, $max_children = 2,
|
||||
$debug = null)
|
||||
{
|
||||
parent::__construct(true); // daemonize
|
||||
|
||||
$this->_interval = $interval;
|
||||
$this->_max_children = $max_children;
|
||||
$this->_debug = $debug;
|
||||
|
||||
if (isset($id)) {
|
||||
$this->set_id($id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the daemon
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function run()
|
||||
{
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . " - Debugging output enabled.\n";
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
$objects = $this->getObjects();
|
||||
|
||||
foreach ($objects as $o) {
|
||||
|
||||
// Fork a child for each object
|
||||
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid == -1) {
|
||||
die ($this->name() . ' - Couldn\'t fork!');
|
||||
}
|
||||
|
||||
if ($pid) {
|
||||
|
||||
// Parent
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() .
|
||||
" - Forked new child - pid $pid.\n";
|
||||
|
||||
}
|
||||
|
||||
$this->_children[] = $pid;
|
||||
|
||||
} else {
|
||||
|
||||
// Child
|
||||
|
||||
// Do something with each object
|
||||
|
||||
$this->childTask($o);
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
// Remove child from ps list as it finishes
|
||||
|
||||
while (($c = pcntl_wait($status, WNOHANG OR WUNTRACED)) > 0) {
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . " - Child $c finished.\n";
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
|
||||
// Wait! We have too many damn kids.
|
||||
|
||||
if (sizeof($this->_children) >= $this->_max_children) {
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . " - Too many children. Waiting...\n";
|
||||
}
|
||||
|
||||
if (($c = pcntl_wait($status, WUNTRACED)) > 0) {
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() .
|
||||
" - Finished waiting for child $c.\n";
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all children from the process list before restarting
|
||||
while (($c = pcntl_wait($status, WUNTRACED)) > 0) {
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . " - Child $c finished.\n";
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
|
||||
// Rest for a bit
|
||||
|
||||
if (isset($this->_debug)) {
|
||||
echo $this->name() . ' - Waiting ' . $this->_interval .
|
||||
" secs before running again.\n";
|
||||
}
|
||||
|
||||
if ($this->_interval > 0) {
|
||||
sleep($this->_interval);
|
||||
}
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child process from the list of children
|
||||
*
|
||||
* @param array &$plist array of processes
|
||||
* @param int $ps process id
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function removePs(&$plist, $ps)
|
||||
{
|
||||
for ($i = 0; $i < sizeof($plist); $i++) {
|
||||
if ($plist[$i] == $ps) {
|
||||
unset($plist[$i]);
|
||||
$plist = array_values($plist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of objects to work on in parallel
|
||||
*
|
||||
* @return array An array of objects to work on
|
||||
*/
|
||||
|
||||
function getObjects()
|
||||
{
|
||||
die('Implement ParallelizingDaemon::getObjects().');
|
||||
}
|
||||
|
||||
/**
|
||||
* Do something with each object in parallel
|
||||
*
|
||||
* @param mixed $object data to work on
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function childTask($object)
|
||||
{
|
||||
die("Implement ParallelizingDaemon::childTask($object).");
|
||||
}
|
||||
|
||||
}
|
@ -88,6 +88,10 @@ class Router
|
||||
|
||||
$m->connect('doc/:title', array('action' => 'doc'));
|
||||
|
||||
// Twitter
|
||||
|
||||
$m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
|
||||
|
||||
// facebook
|
||||
|
||||
$m->connect('facebook', array('action' => 'facebookhome'));
|
||||
|
366
lib/twitter.php
366
lib/twitter.php
@ -17,83 +17,20 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) { exit(1); }
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
|
||||
|
||||
function get_twitter_data($uri, $screen_name, $password)
|
||||
{
|
||||
|
||||
$options = array(
|
||||
CURLOPT_USERPWD => sprintf("%s:%s", $screen_name, $password),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_USERAGENT => "Laconica",
|
||||
CURLOPT_CONNECTTIMEOUT => 120,
|
||||
CURLOPT_TIMEOUT => 120,
|
||||
# Twitter is strict about accepting invalid "Expect" headers
|
||||
CURLOPT_HTTPHEADER => array('Expect:')
|
||||
);
|
||||
|
||||
$ch = curl_init($uri);
|
||||
curl_setopt_array($ch, $options);
|
||||
$data = curl_exec($ch);
|
||||
$errmsg = curl_error($ch);
|
||||
|
||||
if ($errmsg) {
|
||||
common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $screen_name.",
|
||||
__FILE__);
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "cURL error: $errmsg - trying to load: $uri with user $screen_name.\n";
|
||||
}
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function twitter_json_data($uri, $screen_name, $password)
|
||||
{
|
||||
$json_data = get_twitter_data($uri, $screen_name, $password);
|
||||
|
||||
if (!$json_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode($json_data);
|
||||
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
function twitter_user_info($screen_name, $password)
|
||||
{
|
||||
$uri = "http://twitter.com/users/show/$screen_name.json";
|
||||
return twitter_json_data($uri, $screen_name, $password);
|
||||
}
|
||||
|
||||
function twitter_friends_ids($screen_name, $password)
|
||||
{
|
||||
$uri = "http://twitter.com/friends/ids/$screen_name.json";
|
||||
return twitter_json_data($uri, $screen_name, $password);
|
||||
}
|
||||
|
||||
function update_twitter_user($twitter_id, $screen_name)
|
||||
{
|
||||
$uri = 'http://twitter.com/' . $screen_name;
|
||||
|
||||
$fuser = new Foreign_user();
|
||||
|
||||
$fuser->query('BEGIN');
|
||||
|
||||
// Dropping down to SQL because regular db_object udpate stuff doesn't seem
|
||||
// Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem
|
||||
// to work so good with tables that have multiple column primary keys
|
||||
|
||||
// Any time we update the uri for a forein user we have to make sure there
|
||||
@ -102,35 +39,14 @@ function update_twitter_user($twitter_id, $screen_name)
|
||||
$qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
|
||||
$qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
|
||||
|
||||
$result = $fuser->query($qry);
|
||||
|
||||
if ($result) {
|
||||
common_debug("Removed uri ($uri) from another foreign_user who was squatting on it.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print("Removed uri ($uri) from another Twitter user who was squatting on it.\n");
|
||||
}
|
||||
}
|
||||
$fuser->query($qry);
|
||||
|
||||
// Update the user
|
||||
|
||||
$qry = 'UPDATE foreign_user SET nickname = ';
|
||||
$qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
|
||||
$qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
|
||||
|
||||
$result = $fuser->query($qry);
|
||||
|
||||
if (!$result) {
|
||||
common_log(LOG_WARNING,
|
||||
"Couldn't update foreign_user data for Twitter user: $screen_name");
|
||||
common_log_db_error($fuser, 'UPDATE', __FILE__);
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "UPDATE failed: for Twitter user: $twitter_id - $screen_name. - ";
|
||||
print common_log_objstring($fuser) . "\n";
|
||||
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
|
||||
print "DB_DataObject Error: " . $error->getMessage() . "\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$fuser->query('COMMIT');
|
||||
|
||||
$fuser->free();
|
||||
@ -147,23 +63,22 @@ function add_twitter_user($twitter_id, $screen_name)
|
||||
// Clear out any bad old foreign_users with the new user's legit URL
|
||||
// This can happen when users move around or fakester accounts get
|
||||
// repoed, and things like that.
|
||||
|
||||
$luser = new Foreign_user();
|
||||
$luser->uri = $new_uri;
|
||||
$luser->service = TWITTER_SERVICE;
|
||||
$result = $luser->delete();
|
||||
|
||||
if ($result) {
|
||||
if (empty($result)) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Removed invalid Twitter user squatting on uri: $new_uri\n";
|
||||
}
|
||||
}
|
||||
|
||||
$luser->free();
|
||||
unset($luser);
|
||||
|
||||
// Otherwise, create a new Twitter user
|
||||
|
||||
$fuser = new Foreign_user();
|
||||
|
||||
$fuser->nickname = $screen_name;
|
||||
@ -173,21 +88,12 @@ function add_twitter_user($twitter_id, $screen_name)
|
||||
$fuser->created = common_sql_now();
|
||||
$result = $fuser->insert();
|
||||
|
||||
if (!$result) {
|
||||
if (empty($result)) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
|
||||
common_log_db_error($fuser, 'INSERT', __FILE__);
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "INSERT failed: could not add new Twitter user: $twitter_id - $screen_name. - ";
|
||||
print common_log_objstring($fuser) . "\n";
|
||||
$error = &PEAR::getStaticProperty('DB_DataObject','lastError');
|
||||
print "DB_DataObject Error: " . $error->getMessage() . "\n";
|
||||
}
|
||||
} else {
|
||||
common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Added new Twitter user: $screen_name ($twitter_id).\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -199,23 +105,20 @@ function save_twitter_user($twitter_id, $screen_name)
|
||||
|
||||
// Check to see whether the Twitter user is already in the system,
|
||||
// and update its screen name and uri if so.
|
||||
|
||||
$fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
|
||||
|
||||
if ($fuser) {
|
||||
if (!empty($fuser)) {
|
||||
|
||||
$result = true;
|
||||
|
||||
// Only update if Twitter screen name has changed
|
||||
|
||||
if ($fuser->nickname != $screen_name) {
|
||||
$result = update_twitter_user($twitter_id, $screen_name);
|
||||
|
||||
common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
|
||||
"$fuser->id to $screen_name, was $fuser->nickname");
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print 'Updated nickname (and URI) for Twitter user ' .
|
||||
"$fuser->id to $screen_name, was $fuser->nickname\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -230,119 +133,6 @@ function save_twitter_user($twitter_id, $screen_name)
|
||||
return true;
|
||||
}
|
||||
|
||||
function retreive_twitter_friends($twitter_id, $screen_name, $password)
|
||||
{
|
||||
$friends = array();
|
||||
|
||||
$uri = "http://twitter.com/statuses/friends/$twitter_id.json?page=";
|
||||
$friends_ids = twitter_friends_ids($screen_name, $password);
|
||||
|
||||
if (!$friends_ids) {
|
||||
return $friends;
|
||||
}
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Twitter 'social graph' ids method says $screen_name has " .
|
||||
count($friends_ids) . " friends.\n";
|
||||
}
|
||||
|
||||
// Calculate how many pages to get...
|
||||
$pages = ceil(count($friends_ids) / 100);
|
||||
|
||||
if ($pages == 0) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - $screen_name seems to have no friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "$screen_name seems to have no friends.\n";
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $pages; $i++) {
|
||||
|
||||
$data = get_twitter_data($uri . $i, $screen_name, $password);
|
||||
|
||||
if (!$data) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - Couldn't retrieve page $i of $screen_name's friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't retrieve page $i of $screen_name's friends.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$more_friends = json_decode($data);
|
||||
|
||||
if (!$more_friends) {
|
||||
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - No data for page $i of $screen_name's friends.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "No data for page $i of $screen_name's friends.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$friends = array_merge($friends, $more_friends);
|
||||
}
|
||||
|
||||
return $friends;
|
||||
}
|
||||
|
||||
function save_twitter_friends($user, $twitter_id, $screen_name, $password)
|
||||
{
|
||||
|
||||
$friends = retreive_twitter_friends($twitter_id, $screen_name, $password);
|
||||
|
||||
if (empty($friends)) {
|
||||
common_debug("Twitter bridge - Couldn't get friends data from Twitter for $screen_name.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't get friends data from Twitter for $screen_name.\n";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
|
||||
$friend_name = $friend->screen_name;
|
||||
$friend_id = (int) $friend->id;
|
||||
|
||||
// Update or create the Foreign_user record
|
||||
if (!save_twitter_user($friend_id, $friend_name)) {
|
||||
common_log(LOG_WARNING,
|
||||
"Twitter bridge - couldn't save $screen_name's friend, $friend_name.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "Couldn't save $screen_name's friend, $friend_name.\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check to see if there's a related local user
|
||||
$flink = Foreign_link::getByForeignID($friend_id, 1);
|
||||
|
||||
if ($flink) {
|
||||
|
||||
// Get associated user and subscribe her
|
||||
$friend_user = User::staticGet('id', $flink->user_id);
|
||||
if (!empty($friend_user)) {
|
||||
$result = subs_subscribe_to($user, $friend_user);
|
||||
|
||||
if ($result === true) {
|
||||
common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname.");
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print("Subscribed $friend_user->nickname to $user->nickname.\n");
|
||||
}
|
||||
} else {
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "$result ($friend_user->nickname to $user->nickname)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function is_twitter_bound($notice, $flink) {
|
||||
|
||||
// Check to see if notice should go to Twitter
|
||||
@ -351,7 +141,7 @@ function is_twitter_bound($notice, $flink) {
|
||||
// If it's not a Twitter-style reply, or if the user WANTS to send replies.
|
||||
if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
|
||||
($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,104 +150,73 @@ function is_twitter_bound($notice, $flink) {
|
||||
|
||||
function broadcast_twitter($notice)
|
||||
{
|
||||
|
||||
$flink = Foreign_link::getByUserID($notice->profile_id,
|
||||
TWITTER_SERVICE);
|
||||
TWITTER_SERVICE);
|
||||
|
||||
if (is_twitter_bound($notice, $flink)) {
|
||||
|
||||
$fuser = $flink->getForeignUser();
|
||||
$twitter_user = $fuser->nickname;
|
||||
$twitter_password = $flink->credentials;
|
||||
$uri = 'http://www.twitter.com/statuses/update.json';
|
||||
$user = $flink->getUser();
|
||||
|
||||
// XXX: Hack to get around PHP cURL's use of @ being a a meta character
|
||||
$statustxt = preg_replace('/^@/', ' @', $notice->content);
|
||||
|
||||
$options = array(
|
||||
CURLOPT_USERPWD => "$twitter_user:$twitter_password",
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS =>
|
||||
array(
|
||||
'status' => $statustxt,
|
||||
'source' => common_config('integration', 'source')
|
||||
),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_USERAGENT => "Laconica",
|
||||
CURLOPT_CONNECTTIMEOUT => 120, // XXX: How long should this be?
|
||||
CURLOPT_TIMEOUT => 120,
|
||||
$token = TwitterOAuthClient::unpackToken($flink->credentials);
|
||||
|
||||
# Twitter is strict about accepting invalid "Expect" headers
|
||||
CURLOPT_HTTPHEADER => array('Expect:')
|
||||
);
|
||||
$client = new TwitterOAuthClient($token->key, $token->secret);
|
||||
|
||||
$ch = curl_init($uri);
|
||||
curl_setopt_array($ch, $options);
|
||||
$data = curl_exec($ch);
|
||||
$errmsg = curl_error($ch);
|
||||
$errno = curl_errno($ch);
|
||||
$status = null;
|
||||
|
||||
if (!empty($errmsg)) {
|
||||
common_debug("cURL error ($errno): $errmsg - " .
|
||||
"trying to send notice for $twitter_user.",
|
||||
__FILE__);
|
||||
try {
|
||||
$status = $client->statusesUpdate($statustxt);
|
||||
} catch (OAuthClientCurlException $e) {
|
||||
|
||||
$user = $flink->getUser();
|
||||
if ($e->getMessage() == 'The requested URL returned error: 401') {
|
||||
|
||||
if ($errmsg == 'The requested URL returned error: 401') {
|
||||
common_debug(sprintf('User %s (user id: %s) ' .
|
||||
'has bad Twitter credentials!',
|
||||
$user->nickname, $user->id));
|
||||
$errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' .
|
||||
'Twitter OAuth access token.',
|
||||
$user->nickname, $user->id);
|
||||
common_log(LOG_WARNING, $errmsg);
|
||||
|
||||
// Bad credentials we need to delete the foreign_link
|
||||
// to Twitter and inform the user.
|
||||
// Bad auth token! We need to delete the foreign_link
|
||||
// to Twitter and inform the user.
|
||||
|
||||
remove_twitter_link($flink);
|
||||
|
||||
return true;
|
||||
remove_twitter_link($flink);
|
||||
return true;
|
||||
|
||||
} else {
|
||||
|
||||
// Some other error happened, so we should try to
|
||||
// send again later
|
||||
// Some other error happened, so we should probably
|
||||
// try to send again later.
|
||||
|
||||
$errmsg = sprintf('cURL error trying to send notice to Twitter ' .
|
||||
'for user %1$s (user id: %2$s) - ' .
|
||||
'code: %3$s message: $4$s.',
|
||||
$user->nickname, $user->id,
|
||||
$e->getCode(), $e->getMessage());
|
||||
common_log(LOG_WARNING, $errmsg);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
if (empty($status)) {
|
||||
|
||||
if (empty($data)) {
|
||||
common_debug("No data returned by Twitter's " .
|
||||
"API trying to send update for $twitter_user",
|
||||
__FILE__);
|
||||
// This could represent a failure posting,
|
||||
// or the Twitter API might just be behaving flakey.
|
||||
|
||||
// XXX: Not sure this represents a failure to send, but it
|
||||
// probably does
|
||||
$errmsg = sprint('No data returned by Twitter API when ' .
|
||||
'trying to send update for %1$s (user id %2$s).',
|
||||
$user->nickname, $user->id);
|
||||
common_log(LOG_WARNING, $errmsg);
|
||||
|
||||
return false;
|
||||
|
||||
} else {
|
||||
|
||||
// Twitter should return a status
|
||||
$status = json_decode($data);
|
||||
|
||||
if (empty($status)) {
|
||||
common_debug("Unexpected data returned by Twitter " .
|
||||
" API trying to send update for $twitter_user",
|
||||
__FILE__);
|
||||
|
||||
// XXX: Again, this could represent a failure posting
|
||||
// or the Twitter API might just be behaving flakey.
|
||||
// We're treating it as a failure to post.
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Notice crossed the great divide
|
||||
|
||||
$msg = sprintf('Twitter bridge posted notice %s to Twitter.',
|
||||
$notice->id);
|
||||
common_log(LOG_INFO, $msg);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -474,22 +233,25 @@ function remove_twitter_link($flink)
|
||||
|
||||
if (empty($result)) {
|
||||
common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
|
||||
"Foreign_link for $user->nickname (user id: $user->id)!");
|
||||
"Foreign_link for $user->nickname (user id: $user->id)!");
|
||||
common_log_db_error($flink, 'DELETE', __FILE__);
|
||||
}
|
||||
|
||||
// Notify the user that her Twitter bridge is down
|
||||
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
if (isset($user->email)) {
|
||||
|
||||
if (!$result) {
|
||||
$result = mail_twitter_bridge_removed($user);
|
||||
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
if (!$result) {
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
$msg = 'Unable to send email to notify ' .
|
||||
"$user->nickname (user id: $user->id) " .
|
||||
'that their Twitter bridge link was ' .
|
||||
'removed!';
|
||||
|
||||
common_log(LOG_WARNING, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
220
lib/twitteroauthclient.php
Normal file
220
lib/twitteroauthclient.php
Normal file
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Laconica, the distributed open-source microblogging tool
|
||||
*
|
||||
* Class for doing OAuth calls against Twitter
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Integration
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @copyright 2008 Control Yourself, Inc.
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
if (!defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for talking to the Twitter API with OAuth.
|
||||
*
|
||||
* @category Integration
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*
|
||||
*/
|
||||
class TwitterOAuthClient extends OAuthClient
|
||||
{
|
||||
public static $requestTokenURL = 'https://twitter.com/oauth/request_token';
|
||||
public static $authorizeURL = 'https://twitter.com/oauth/authorize';
|
||||
public static $accessTokenURL = 'https://twitter.com/oauth/access_token';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $oauth_token the user's token
|
||||
* @param string $oauth_token_secret the user's token secret
|
||||
*
|
||||
* @return nothing
|
||||
*/
|
||||
function __construct($oauth_token = null, $oauth_token_secret = null)
|
||||
{
|
||||
$consumer_key = common_config('twitter', 'consumer_key');
|
||||
$consumer_secret = common_config('twitter', 'consumer_secret');
|
||||
|
||||
parent::__construct($consumer_key, $consumer_secret,
|
||||
$oauth_token, $oauth_token_secret);
|
||||
}
|
||||
|
||||
// XXX: the following two functions are to support the horrible hack
|
||||
// of using the credentils field in Foreign_link to store both
|
||||
// the access token and token secret. This hack should go away with
|
||||
// 0.9, in which we can make DB changes and add a new column for the
|
||||
// token itself.
|
||||
|
||||
static function packToken($token)
|
||||
{
|
||||
return implode(chr(0), array($token->key, $token->secret));
|
||||
}
|
||||
|
||||
static function unpackToken($str)
|
||||
{
|
||||
$vals = explode(chr(0), $str);
|
||||
return new OAuthToken($vals[0], $vals[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a link to Twitter's endpoint for authorizing a request token
|
||||
*
|
||||
* @param OAuthToken $request_token token to authorize
|
||||
*
|
||||
* @return the link
|
||||
*/
|
||||
function getAuthorizeLink($request_token)
|
||||
{
|
||||
return parent::getAuthorizeLink(self::$authorizeURL,
|
||||
$request_token,
|
||||
common_local_url('twitterauthorization'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Twitter's /account/verify_credentials API method
|
||||
*
|
||||
* @return mixed the Twitter user
|
||||
*/
|
||||
function verifyCredentials()
|
||||
{
|
||||
$url = 'https://twitter.com/account/verify_credentials.json';
|
||||
$response = $this->oAuthGet($url);
|
||||
$twitter_user = json_decode($response);
|
||||
return $twitter_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Twitter's /stutuses/update API method
|
||||
*
|
||||
* @param string $status text of the status
|
||||
* @param int $in_reply_to_status_id optional id of the status it's
|
||||
* a reply to
|
||||
*
|
||||
* @return mixed the status
|
||||
*/
|
||||
function statusesUpdate($status, $in_reply_to_status_id = null)
|
||||
{
|
||||
$url = 'https://twitter.com/statuses/update.json';
|
||||
$params = array('status' => $status,
|
||||
'in_reply_to_status_id' => $in_reply_to_status_id);
|
||||
$response = $this->oAuthPost($url, $params);
|
||||
$status = json_decode($response);
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Twitter's /stutuses/friends_timeline API method
|
||||
*
|
||||
* @param int $since_id show statuses after this id
|
||||
* @param int $max_id show statuses before this id
|
||||
* @param int $cnt number of statuses to show
|
||||
* @param int $page page number
|
||||
*
|
||||
* @return mixed an array of statuses
|
||||
*/
|
||||
function statusesFriendsTimeline($since_id = null, $max_id = null,
|
||||
$cnt = null, $page = null)
|
||||
{
|
||||
|
||||
$url = 'https://twitter.com/statuses/friends_timeline.json';
|
||||
$params = array('since_id' => $since_id,
|
||||
'max_id' => $max_id,
|
||||
'count' => $cnt,
|
||||
'page' => $page);
|
||||
$qry = http_build_query($params);
|
||||
|
||||
if (!empty($qry)) {
|
||||
$url .= "?$qry";
|
||||
}
|
||||
|
||||
$response = $this->oAuthGet($url);
|
||||
$statuses = json_decode($response);
|
||||
return $statuses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Twitter's /stutuses/friends API method
|
||||
*
|
||||
* @param int $id id of the user whom you wish to see friends of
|
||||
* @param int $user_id numerical user id
|
||||
* @param int $screen_name screen name
|
||||
* @param int $page page number
|
||||
*
|
||||
* @return mixed an array of twitter users and their latest status
|
||||
*/
|
||||
function statusesFriends($id = null, $user_id = null, $screen_name = null,
|
||||
$page = null)
|
||||
{
|
||||
$url = "https://twitter.com/statuses/friends.json";
|
||||
|
||||
$params = array('id' => $id,
|
||||
'user_id' => $user_id,
|
||||
'screen_name' => $screen_name,
|
||||
'page' => $page);
|
||||
$qry = http_build_query($params);
|
||||
|
||||
if (!empty($qry)) {
|
||||
$url .= "?$qry";
|
||||
}
|
||||
|
||||
$response = $this->oAuthGet($url);
|
||||
$friends = json_decode($response);
|
||||
return $friends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Twitter's /stutuses/friends/ids API method
|
||||
*
|
||||
* @param int $id id of the user whom you wish to see friends of
|
||||
* @param int $user_id numerical user id
|
||||
* @param int $screen_name screen name
|
||||
* @param int $page page number
|
||||
*
|
||||
* @return mixed a list of ids, 100 per page
|
||||
*/
|
||||
function friendsIds($id = null, $user_id = null, $screen_name = null,
|
||||
$page = null)
|
||||
{
|
||||
$url = "https://twitter.com/friends/ids.json";
|
||||
|
||||
$params = array('id' => $id,
|
||||
'user_id' => $user_id,
|
||||
'screen_name' => $screen_name,
|
||||
'page' => $page);
|
||||
$qry = http_build_query($params);
|
||||
|
||||
if (!empty($qry)) {
|
||||
$url .= "?$qry";
|
||||
}
|
||||
|
||||
$response = $this->oAuthGet($url);
|
||||
$ids = json_decode($response);
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
@ -45,6 +45,7 @@ if(common_config('twitterbridge','enabled')) {
|
||||
echo "ombqueuehandler.php ";
|
||||
if (common_config('twitter', 'enabled')) {
|
||||
echo "twitterqueuehandler.php ";
|
||||
echo "synctwitterfriends.php ";
|
||||
}
|
||||
echo "facebookqueuehandler.php ";
|
||||
echo "pingqueuehandler.php ";
|
||||
|
@ -25,7 +25,7 @@ DIR=`php $SDIR/getpiddir.php`
|
||||
|
||||
for f in jabberhandler ombhandler publichandler smshandler pinghandler \
|
||||
xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \
|
||||
twitterstatusfetcher; do
|
||||
twitterstatusfetcher synctwitterfriends; do
|
||||
|
||||
FILES="$DIR/$f.*.pid"
|
||||
for ff in "$FILES" ; do
|
||||
|
@ -20,85 +20,260 @@
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
|
||||
|
||||
// Uncomment this to get useful console output
|
||||
$shortoptions = 'di::';
|
||||
$longoptions = array('id::', 'debug');
|
||||
|
||||
$helptext = <<<END_OF_TRIM_HELP
|
||||
Batch script for synching local friends with Twitter friends.
|
||||
-i --id Identity (default 'generic')
|
||||
-d --debug Debug (lots of log output)
|
||||
|
||||
END_OF_TRIM_HELP;
|
||||
|
||||
require_once INSTALLDIR . '/scripts/commandline.inc';
|
||||
require_once INSTALLDIR . '/lib/parallelizingdaemon.php';
|
||||
|
||||
/**
|
||||
* Daemon to sync local friends with Twitter friends
|
||||
*
|
||||
* @category Twitter
|
||||
* @package Laconica
|
||||
* @author Zach Copley <zach@controlyourself.ca>
|
||||
* @author Evan Prodromou <evan@controlyourself.ca>
|
||||
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
||||
* @link http://laconi.ca/
|
||||
*/
|
||||
|
||||
$helptext = <<<END_OF_TWITTER_HELP
|
||||
Batch script for synching local friends with Twitter friends.
|
||||
|
||||
END_OF_TWITTER_HELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
require_once INSTALLDIR . '/scripts/commandline.inc';
|
||||
require_once INSTALLDIR . '/lib/parallelizingdaemon.php';
|
||||
|
||||
// Make a lockfile
|
||||
$lockfilename = lockFilename();
|
||||
if (!($lockfile = @fopen($lockfilename, "w"))) {
|
||||
print "Already running... exiting.\n";
|
||||
exit(1);
|
||||
}
|
||||
class SyncTwitterFriendsDaemon extends ParallelizingDaemon
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $id the name/id of this daemon
|
||||
* @param int $interval sleep this long before doing everything again
|
||||
* @param int $max_children maximum number of child processes at a time
|
||||
* @param boolean $debug debug output flag
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
**/
|
||||
|
||||
// Obtain an exlcusive lock on file (will fail if script is already going)
|
||||
if (!@flock( $lockfile, LOCK_EX | LOCK_NB, &$wouldblock) || $wouldblock) {
|
||||
// Script already running - abort
|
||||
@fclose($lockfile);
|
||||
print "Already running... exiting.\n";
|
||||
exit(1);
|
||||
}
|
||||
function __construct($id = null, $interval = 60,
|
||||
$max_children = 2, $debug = null)
|
||||
{
|
||||
parent::__construct($id, $interval, $max_children, $debug);
|
||||
}
|
||||
|
||||
$flink = new Foreign_link();
|
||||
$flink->service = 1; // Twitter
|
||||
$flink->orderBy('last_friendsync');
|
||||
$flink->limit(25); // sync this many users during this run
|
||||
$cnt = $flink->find();
|
||||
/**
|
||||
* Name of this daemon
|
||||
*
|
||||
* @return string Name of the daemon.
|
||||
*/
|
||||
|
||||
print "Updating Twitter friends subscriptions for $cnt users.\n";
|
||||
function name()
|
||||
{
|
||||
return ('synctwitterfriends.' . $this->_id);
|
||||
}
|
||||
|
||||
while ($flink->fetch()) {
|
||||
/**
|
||||
* Find all the Twitter foreign links for users who have requested
|
||||
* automatically subscribing to their Twitter friends locally.
|
||||
*
|
||||
* @return array flinks an array of Foreign_link objects
|
||||
*/
|
||||
function getObjects()
|
||||
{
|
||||
$flinks = array();
|
||||
$flink = new Foreign_link();
|
||||
|
||||
if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) {
|
||||
$conn = &$flink->getDatabaseConnection();
|
||||
|
||||
$user = User::staticGet($flink->user_id);
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
$flink->orderBy('last_friendsync');
|
||||
$flink->limit(25); // sync this many users during this run
|
||||
$flink->find();
|
||||
|
||||
if (empty($user)) {
|
||||
common_log(LOG_WARNING, "Unmatched user for ID " . $flink->user_id);
|
||||
print "Unmatched user for ID $flink->user_id\n";
|
||||
continue;
|
||||
while ($flink->fetch()) {
|
||||
if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) {
|
||||
$flinks[] = clone($flink);
|
||||
}
|
||||
}
|
||||
|
||||
print "Updating Twitter friends for $user->nickname (Laconica ID: $user->id)... ";
|
||||
$conn->disconnect();
|
||||
|
||||
$fuser = $flink->getForeignUser();
|
||||
global $_DB_DATAOBJECT;
|
||||
unset($_DB_DATAOBJECT['CONNECTIONS']);
|
||||
|
||||
if (empty($fuser)) {
|
||||
common_log(LOG_WARNING, "Unmatched user for ID " . $flink->user_id);
|
||||
print "Unmatched user for ID $flink->user_id\n";
|
||||
continue;
|
||||
}
|
||||
return $flinks;
|
||||
}
|
||||
|
||||
save_twitter_friends($user, $fuser->id, $fuser->nickname, $flink->credentials);
|
||||
function childTask($flink) {
|
||||
|
||||
// Each child ps needs its own DB connection
|
||||
|
||||
// Note: DataObject::getDatabaseConnection() creates
|
||||
// a new connection if there isn't one already
|
||||
|
||||
$conn = &$flink->getDatabaseConnection();
|
||||
|
||||
$this->subscribeTwitterFriends($flink);
|
||||
|
||||
$flink->last_friendsync = common_sql_now();
|
||||
$flink->update();
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
print "\nDONE\n";
|
||||
} else {
|
||||
print "DONE\n";
|
||||
$conn->disconnect();
|
||||
|
||||
// XXX: Couldn't find a less brutal way to blow
|
||||
// away a cached connection
|
||||
|
||||
global $_DB_DATAOBJECT;
|
||||
unset($_DB_DATAOBJECT['CONNECTIONS']);
|
||||
}
|
||||
|
||||
function fetchTwitterFriends($flink)
|
||||
{
|
||||
$friends = array();
|
||||
|
||||
$token = TwitterOAuthClient::unpackToken($flink->credentials);
|
||||
|
||||
$client = new TwitterOAuthClient($token->key, $token->secret);
|
||||
|
||||
try {
|
||||
$friends_ids = $client->friendsIds();
|
||||
} catch (OAuthCurlException $e) {
|
||||
common_log(LOG_WARNING, $this->name() .
|
||||
' - cURL error getting friend ids ' .
|
||||
$e->getCode() . ' - ' . $e->getMessage());
|
||||
return $friends;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function lockFilename()
|
||||
{
|
||||
$piddir = common_config('daemon', 'piddir');
|
||||
if (!$piddir) {
|
||||
$piddir = '/var/run';
|
||||
if (empty($friends_ids)) {
|
||||
common_debug($this->name() .
|
||||
" - Twitter user $flink->foreign_id " .
|
||||
'doesn\'t have any friends!');
|
||||
return $friends;
|
||||
}
|
||||
|
||||
common_debug($this->name() . ' - Twitter\'s API says Twitter user id ' .
|
||||
"$flink->foreign_id has " .
|
||||
count($friends_ids) . ' friends.');
|
||||
|
||||
// Calculate how many pages to get...
|
||||
$pages = ceil(count($friends_ids) / 100);
|
||||
|
||||
if ($pages == 0) {
|
||||
common_debug($this->name() . " - $user seems to have no friends.");
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $pages; $i++) {
|
||||
|
||||
try {
|
||||
$more_friends = $client->statusesFriends(null, null, null, $i);
|
||||
} catch (OAuthCurlException $e) {
|
||||
common_log(LOG_WARNING, $this->name() .
|
||||
' - cURL error getting Twitter statuses/friends ' .
|
||||
"page $i - " . $e->getCode() . ' - ' .
|
||||
$e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($more_friends)) {
|
||||
common_log(LOG_WARNING, $this->name() .
|
||||
" - Couldn't retrieve page $i " .
|
||||
"of Twitter user $flink->foreign_id friends.");
|
||||
continue;
|
||||
} else {
|
||||
$friends = array_merge($friends, $more_friends);
|
||||
}
|
||||
}
|
||||
|
||||
return $friends;
|
||||
}
|
||||
|
||||
function subscribeTwitterFriends($flink)
|
||||
{
|
||||
$friends = $this->fetchTwitterFriends($flink);
|
||||
|
||||
if (empty($friends)) {
|
||||
common_debug($this->name() .
|
||||
' - Couldn\'t get friends from Twitter for ' .
|
||||
"Twitter user $flink->foreign_id.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = $flink->getUser();
|
||||
|
||||
foreach ($friends as $friend) {
|
||||
|
||||
$friend_name = $friend->screen_name;
|
||||
$friend_id = (int) $friend->id;
|
||||
|
||||
// Update or create the Foreign_user record for each
|
||||
// Twitter friend
|
||||
|
||||
if (!save_twitter_user($friend_id, $friend_name)) {
|
||||
common_log(LOG_WARNING, $this-name() .
|
||||
" - Couldn't save $screen_name's friend, $friend_name.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check to see if there's a related local user
|
||||
|
||||
$friend_flink = Foreign_link::getByForeignID($friend_id,
|
||||
TWITTER_SERVICE);
|
||||
|
||||
if (!empty($friend_flink)) {
|
||||
|
||||
// Get associated user and subscribe her
|
||||
|
||||
$friend_user = User::staticGet('id', $friend_flink->user_id);
|
||||
|
||||
if (!empty($friend_user)) {
|
||||
$result = subs_subscribe_to($user, $friend_user);
|
||||
|
||||
if ($result === true) {
|
||||
common_log(LOG_INFO,
|
||||
$this->name() . ' - Subscribed ' .
|
||||
"$friend_user->nickname to $user->nickname.");
|
||||
} else {
|
||||
common_debug($this->name() .
|
||||
' - Tried subscribing ' .
|
||||
"$friend_user->nickname to $user->nickname - " .
|
||||
$result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return $piddir . '/synctwitterfriends.lock';
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
fclose($lockfile);
|
||||
unlink($lockfilename);
|
||||
$id = null;
|
||||
$debug = null;
|
||||
|
||||
if (have_option('i')) {
|
||||
$id = get_option_value('i');
|
||||
} else if (have_option('--id')) {
|
||||
$id = get_option_value('--id');
|
||||
} else if (count($args) > 0) {
|
||||
$id = $args[0];
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
|
||||
if (have_option('d') || have_option('debug')) {
|
||||
$debug = true;
|
||||
}
|
||||
|
||||
$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug);
|
||||
$syncer->runOnce();
|
||||
|
||||
exit(0);
|
||||
|
@ -56,17 +56,23 @@ require_once INSTALLDIR . '/lib/daemon.php';
|
||||
// NOTE: an Avatar path MUST be set in config.php for this
|
||||
// script to work: e.g.: $config['avatar']['path'] = '/laconica/avatar';
|
||||
|
||||
class TwitterStatusFetcher extends Daemon
|
||||
class TwitterStatusFetcher extends ParallelizingDaemon
|
||||
{
|
||||
private $_children = array();
|
||||
|
||||
function __construct($id=null, $daemonize=true)
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $id the name/id of this daemon
|
||||
* @param int $interval sleep this long before doing everything again
|
||||
* @param int $max_children maximum number of child processes at a time
|
||||
* @param boolean $debug debug output flag
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
**/
|
||||
function __construct($id = null, $interval = 60,
|
||||
$max_children = 2, $debug = null)
|
||||
{
|
||||
parent::__construct($daemonize);
|
||||
|
||||
if ($id) {
|
||||
$this->set_id($id);
|
||||
}
|
||||
parent::__construct($id, $interval, $max_children, $debug);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,126 +87,22 @@ class TwitterStatusFetcher extends Daemon
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the daemon
|
||||
* Find all the Twitter foreign links for users who have requested
|
||||
* importing of their friends' timelines
|
||||
*
|
||||
* @return void
|
||||
* @return array flinks an array of Foreign_link objects
|
||||
*/
|
||||
|
||||
function run()
|
||||
function getObjects()
|
||||
{
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug($this->name() .
|
||||
': debugging log output enabled.');
|
||||
}
|
||||
global $_DB_DATAOBJECT;
|
||||
|
||||
do {
|
||||
|
||||
$flinks = $this->refreshFlinks();
|
||||
|
||||
foreach ($flinks as $f) {
|
||||
|
||||
// We have to disconnect from the DB before forking so
|
||||
// each sub-process will open its own connection and
|
||||
// avoid stomping on the others
|
||||
|
||||
$conn = &$f->getDatabaseConnection();
|
||||
$conn->disconnect();
|
||||
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid == -1) {
|
||||
die ("Couldn't fork!");
|
||||
}
|
||||
|
||||
if ($pid) {
|
||||
|
||||
// Parent
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Parent: forked new status ".
|
||||
" fetcher process " . $pid);
|
||||
}
|
||||
|
||||
$this->_children[] = $pid;
|
||||
|
||||
} else {
|
||||
|
||||
// Child
|
||||
$this->getTimeline($f);
|
||||
exit();
|
||||
}
|
||||
|
||||
// Remove child from ps list as it finishes
|
||||
while (($c = pcntl_wait($status, WNOHANG OR WUNTRACED)) > 0) {
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Child $c finished.");
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
|
||||
// Wait! We have too many damn kids.
|
||||
if (sizeof($this->_children) > MAXCHILDREN) {
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug('Too many children. Waiting...');
|
||||
}
|
||||
|
||||
if (($c = pcntl_wait($status, WUNTRACED)) > 0) {
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Finished waiting for $c");
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all children from the process list before restarting
|
||||
while (($c = pcntl_wait($status, WUNTRACED)) > 0) {
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Child $c finished.");
|
||||
}
|
||||
|
||||
$this->removePs($this->_children, $c);
|
||||
}
|
||||
|
||||
// Rest for a bit before we fetch more statuses
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug('Waiting ' . POLL_INTERVAL .
|
||||
' secs before hitting Twitter again.');
|
||||
}
|
||||
|
||||
if (POLL_INTERVAL > 0) {
|
||||
sleep(POLL_INTERVAL);
|
||||
}
|
||||
|
||||
} while (true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the foreign links for this user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function refreshFlinks()
|
||||
{
|
||||
$flink = new Foreign_link();
|
||||
$conn = &$flink->getDatabaseConnection();
|
||||
|
||||
$flink->service = 1; // Twitter
|
||||
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
$flink->orderBy('last_noticesync');
|
||||
|
||||
$cnt = $flink->find();
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug('Updating Twitter friends subscriptions' .
|
||||
" for $cnt users.");
|
||||
}
|
||||
$flink->find();
|
||||
|
||||
$flinks = array();
|
||||
|
||||
@ -215,78 +117,81 @@ class TwitterStatusFetcher extends Daemon
|
||||
$flink->free();
|
||||
unset($flink);
|
||||
|
||||
$conn->disconnect();
|
||||
unset($_DB_DATAOBJECT['CONNECTIONS']);
|
||||
|
||||
return $flinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown
|
||||
*
|
||||
* @param array &$plist unknown.
|
||||
* @param string $ps unknown.
|
||||
*
|
||||
* @return unknown
|
||||
* @todo document
|
||||
*/
|
||||
function childTask($flink) {
|
||||
|
||||
function removePs(&$plist, $ps)
|
||||
{
|
||||
for ($i = 0; $i < sizeof($plist); $i++) {
|
||||
if ($plist[$i] == $ps) {
|
||||
unset($plist[$i]);
|
||||
$plist = array_values($plist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Each child ps needs its own DB connection
|
||||
|
||||
// Note: DataObject::getDatabaseConnection() creates
|
||||
// a new connection if there isn't one already
|
||||
|
||||
$conn = &$flink->getDatabaseConnection();
|
||||
|
||||
$this->getTimeline($flink);
|
||||
|
||||
$flink->last_friendsync = common_sql_now();
|
||||
$flink->update();
|
||||
|
||||
$conn->disconnect();
|
||||
|
||||
// XXX: Couldn't find a less brutal way to blow
|
||||
// away a cached connection
|
||||
|
||||
global $_DB_DATAOBJECT;
|
||||
unset($_DB_DATAOBJECT['CONNECTIONS']);
|
||||
}
|
||||
|
||||
function getTimeline($flink)
|
||||
{
|
||||
if (empty($flink)) {
|
||||
common_log(LOG_WARNING,
|
||||
"Can't retrieve Foreign_link for foreign ID $fid");
|
||||
if (empty($flink)) {
|
||||
common_log(LOG_WARNING, $this->name() .
|
||||
" - Can't retrieve Foreign_link for foreign ID $fid");
|
||||
return;
|
||||
}
|
||||
|
||||
$fuser = $flink->getForeignUser();
|
||||
|
||||
if (empty($fuser)) {
|
||||
common_log(LOG_WARNING, "Unmatched user for ID " .
|
||||
$flink->user_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug('Trying to get timeline for Twitter user ' .
|
||||
"$fuser->nickname ($flink->foreign_id).");
|
||||
}
|
||||
common_debug($this->name() . ' - Trying to get timeline for Twitter user ' .
|
||||
$flink->foreign_id);
|
||||
|
||||
// XXX: Biggest remaining issue - How do we know at which status
|
||||
// to start importing? How many statuses? Right now I'm going
|
||||
// with the default last 20.
|
||||
|
||||
$url = 'http://twitter.com/statuses/friends_timeline.json';
|
||||
$token = TwitterOAuthClient::unpackToken($flink->credentials);
|
||||
|
||||
$timeline_json = get_twitter_data($url, $fuser->nickname,
|
||||
$flink->credentials);
|
||||
$client = new TwitterOAuthClient($token->key, $token->secret);
|
||||
|
||||
$timeline = json_decode($timeline_json);
|
||||
$timeline = null;
|
||||
|
||||
try {
|
||||
$timeline = $client->statusesFriendsTimeline();
|
||||
} catch (OAuthClientCurlException $e) {
|
||||
common_log(LOG_WARNING, $this->name() .
|
||||
' - OAuth client unable to get friends timeline for user ' .
|
||||
$flink->user_id . ' - code: ' .
|
||||
$e->getCode() . 'msg: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($timeline)) {
|
||||
common_log(LOG_WARNING, "Empty timeline.");
|
||||
common_log(LOG_WARNING, $this->name() . " - Empty timeline.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reverse to preserve order
|
||||
|
||||
foreach (array_reverse($timeline) as $status) {
|
||||
|
||||
// Hacktastic: filter out stuff coming from this Laconica
|
||||
|
||||
$source = mb_strtolower(common_config('integration', 'source'));
|
||||
|
||||
if (preg_match("/$source/", mb_strtolower($status->source))) {
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug('Skipping import of status ' . $status->id .
|
||||
' with source ' . $source);
|
||||
}
|
||||
common_debug($this->name() . ' - Skipping import of status ' .
|
||||
$status->id . ' with source ' . $source);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -294,6 +199,7 @@ class TwitterStatusFetcher extends Daemon
|
||||
}
|
||||
|
||||
// Okay, record the time we synced with Twitter for posterity
|
||||
|
||||
$flink->last_noticesync = common_sql_now();
|
||||
$flink->update();
|
||||
}
|
||||
@ -301,11 +207,12 @@ class TwitterStatusFetcher extends Daemon
|
||||
function saveStatus($status, $flink)
|
||||
{
|
||||
$id = $this->ensureProfile($status->user);
|
||||
|
||||
$profile = Profile::staticGet($id);
|
||||
|
||||
if (!$profile) {
|
||||
common_log(LOG_ERR,
|
||||
'Problem saving notice. No associated Profile.');
|
||||
if (empty($profile)) {
|
||||
common_log(LOG_ERR, $this->name() .
|
||||
' - Problem saving notice. No associated Profile.');
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -318,7 +225,7 @@ class TwitterStatusFetcher extends Daemon
|
||||
|
||||
// check to see if we've already imported the status
|
||||
|
||||
if (!$notice) {
|
||||
if (empty($notice)) {
|
||||
|
||||
$notice = new Notice();
|
||||
|
||||
@ -329,7 +236,7 @@ class TwitterStatusFetcher extends Daemon
|
||||
$notice->content = common_shorten_links($status->text); // XXX
|
||||
$notice->rendered = common_render_content($notice->content, $notice);
|
||||
$notice->source = 'twitter';
|
||||
$notice->reply_to = null; // XXX lookup reply
|
||||
$notice->reply_to = null; // XXX: lookup reply
|
||||
$notice->is_local = Notice::GATEWAY;
|
||||
|
||||
if (Event::handle('StartNoticeSave', array(&$notice))) {
|
||||
@ -355,24 +262,22 @@ class TwitterStatusFetcher extends Daemon
|
||||
function ensureProfile($user)
|
||||
{
|
||||
// check to see if there's already a profile for this user
|
||||
|
||||
$profileurl = 'http://twitter.com/' . $user->screen_name;
|
||||
$profile = Profile::staticGet('profileurl', $profileurl);
|
||||
|
||||
if ($profile) {
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Profile for $profile->nickname found.");
|
||||
}
|
||||
if (!empty($profile)) {
|
||||
common_debug($this->name() .
|
||||
" - Profile for $profile->nickname found.");
|
||||
|
||||
// Check to see if the user's Avatar has changed
|
||||
$this->checkAvatar($user, $profile);
|
||||
|
||||
$this->checkAvatar($user, $profile);
|
||||
return $profile->id;
|
||||
|
||||
} else {
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug('Adding profile and remote profile ' .
|
||||
"for Twitter user: $profileurl");
|
||||
}
|
||||
common_debug($this->name() . ' - Adding profile and remote profile ' .
|
||||
"for Twitter user: $profileurl.");
|
||||
|
||||
$profile = new Profile();
|
||||
$profile->query("BEGIN");
|
||||
@ -394,9 +299,10 @@ class TwitterStatusFetcher extends Daemon
|
||||
}
|
||||
|
||||
// check for remote profile
|
||||
|
||||
$remote_pro = Remote_profile::staticGet('uri', $profileurl);
|
||||
|
||||
if (!$remote_pro) {
|
||||
if (empty($remote_pro)) {
|
||||
|
||||
$remote_pro = new Remote_profile();
|
||||
|
||||
@ -433,23 +339,18 @@ class TwitterStatusFetcher extends Daemon
|
||||
$oldname = $profile->getAvatar(48)->filename;
|
||||
|
||||
if ($newname != $oldname) {
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug('Avatar for Twitter user ' .
|
||||
"$profile->nickname has changed.");
|
||||
common_debug("old: $oldname new: $newname");
|
||||
}
|
||||
common_debug($this->name() . ' - Avatar for Twitter user ' .
|
||||
"$profile->nickname has changed.");
|
||||
common_debug($this->name() . " - old: $oldname new: $newname");
|
||||
|
||||
$this->updateAvatars($twitter_user, $profile);
|
||||
}
|
||||
|
||||
if ($this->missingAvatarFile($profile)) {
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug('Twitter user ' . $profile->nickname .
|
||||
' is missing one or more local avatars.');
|
||||
common_debug("old: $oldname new: $newname");
|
||||
}
|
||||
common_debug($this->name() . ' - Twitter user ' .
|
||||
$profile->nickname .
|
||||
' is missing one or more local avatars.');
|
||||
common_debug($this->name() ." - old: $oldname new: $newname");
|
||||
|
||||
$this->updateAvatars($twitter_user, $profile);
|
||||
}
|
||||
@ -529,23 +430,20 @@ class TwitterStatusFetcher extends Daemon
|
||||
if ($this->fetchAvatar($url, $filename)) {
|
||||
$this->newAvatar($id, $size, $mediatype, $filename);
|
||||
} else {
|
||||
common_log(LOG_WARNING, "Problem fetching Avatar: $url", __FILE__);
|
||||
common_log(LOG_WARNING, $this->id() .
|
||||
" - Problem fetching Avatar: $url");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateAvatar($profile_id, $size, $mediatype, $filename) {
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Updating avatar: $size");
|
||||
}
|
||||
common_debug($this->name() . " - Updating avatar: $size");
|
||||
|
||||
$profile = Profile::staticGet($profile_id);
|
||||
|
||||
if (empty($profile)) {
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Couldn't get profile: $profile_id!");
|
||||
}
|
||||
common_debug($this->name() . " - Couldn't get profile: $profile_id!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -553,6 +451,7 @@ class TwitterStatusFetcher extends Daemon
|
||||
$avatar = $profile->getAvatar($sizes[$size]);
|
||||
|
||||
// Delete the avatar, if present
|
||||
|
||||
if ($avatar) {
|
||||
$avatar->delete();
|
||||
}
|
||||
@ -590,9 +489,7 @@ class TwitterStatusFetcher extends Daemon
|
||||
$avatar->filename = $filename;
|
||||
$avatar->url = Avatar::url($filename);
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("new filename: $avatar->url");
|
||||
}
|
||||
common_debug($this->name() . " - New filename: $avatar->url");
|
||||
|
||||
$avatar->created = common_sql_now();
|
||||
|
||||
@ -603,9 +500,8 @@ class TwitterStatusFetcher extends Daemon
|
||||
return null;
|
||||
}
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Saved new $size avatar for $profile_id.");
|
||||
}
|
||||
common_debug($this->name() .
|
||||
" - Saved new $size avatar for $profile_id.");
|
||||
|
||||
return $id;
|
||||
}
|
||||
@ -618,13 +514,12 @@ class TwitterStatusFetcher extends Daemon
|
||||
|
||||
$out = fopen($avatarfile, 'wb');
|
||||
if (!$out) {
|
||||
common_log(LOG_WARNING, "Couldn't open file $filename", __FILE__);
|
||||
common_log(LOG_WARNING, $this->name() .
|
||||
" - Couldn't open file $filename");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (defined('SCRIPT_DEBUG')) {
|
||||
common_debug("Fetching avatar: $url");
|
||||
}
|
||||
common_debug($this->name() . " - Fetching Twitter avatar: $url");
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
@ -641,7 +536,8 @@ class TwitterStatusFetcher extends Daemon
|
||||
}
|
||||
}
|
||||
|
||||
declare(ticks = 1);
|
||||
$id = null;
|
||||
$debug = null;
|
||||
|
||||
if (have_option('i')) {
|
||||
$id = get_option_value('i');
|
||||
@ -654,9 +550,9 @@ if (have_option('i')) {
|
||||
}
|
||||
|
||||
if (have_option('d') || have_option('debug')) {
|
||||
define('SCRIPT_DEBUG', true);
|
||||
$debug = true;
|
||||
}
|
||||
|
||||
$fetcher = new TwitterStatusFetcher($id);
|
||||
$fetcher = new TwitterStatusFetcher($id, 60, 2, $debug);
|
||||
$fetcher->runOnce();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user