Merge remote branch 'laconica/0.8.x' into 0.9.x

Conflicts:
	lib/common.php
	lib/twitter.php
This commit is contained in:
Craig Andrews 2009-09-02 16:42:15 -04:00
commit a535ccdc4e
25 changed files with 736 additions and 179 deletions

1
README
View File

@ -146,6 +146,7 @@ Your PHP installation must include the following PHP extensions:
- GD. For scaling down avatar images.
- mbstring. For handling Unicode (UTF-8) encoded strings.
- gettext. For multiple languages. Default on many PHP installs.
- tidy. Used to clean up HTML/URLs for the URL shortener to consume.
For some functionality, you will also need the following extensions:

View File

@ -133,6 +133,8 @@ class ApiAction extends Action
'groups/show',
'groups/timeline',
'groups/list_all',
'groups/membership',
'groups/is_member',
'groups/timeline');
static $bareauth = array('statuses/user_timeline',

View File

@ -146,8 +146,10 @@ class FoafAction extends Action
while ($sub->fetch()) {
if ($sub->token) {
$other = Remote_profile::staticGet('id', $sub->subscriber);
$profile = Profile::staticGet('id', $sub->subscriber);
} else {
$other = User::staticGet('id', $sub->subscriber);
$profile = Profile::staticGet('id', $sub->subscriber);
}
if (!$other) {
common_debug('Got a bad subscription: '.print_r($sub,true));
@ -158,12 +160,15 @@ class FoafAction extends Action
} else {
$person[$other->uri] = array(LISTENER,
$other->id,
$other->nickname,
$profile->nickname,
(empty($sub->token)) ? 'User' : 'Remote_profile');
}
$other->free();
$other = null;
unset($other);
$profile->free();
$profile = null;
unset($profile);
}
}
@ -254,8 +259,10 @@ class FoafAction extends Action
while ($sub->fetch()) {
if (!empty($sub->token)) {
$other = Remote_profile::staticGet('id', $sub->subscribed);
$profile = Profile::staticGet('id', $sub->subscribed);
} else {
$other = User::staticGet('id', $sub->subscribed);
$profile = Profile::staticGet('id', $sub->subscribed);
}
if (empty($other)) {
common_debug('Got a bad subscription: '.print_r($sub,true));
@ -264,11 +271,14 @@ class FoafAction extends Action
$this->element('sioc:follows', array('rdf:resource' => $other->uri.'#acct'));
$person[$other->uri] = array(LISTENEE,
$other->id,
$other->nickname,
$profile->nickname,
(empty($sub->token)) ? 'User' : 'Remote_profile');
$other->free();
$other = null;
unset($other);
$profile->free();
$profile = null;
unset($profile);
}
}

View File

@ -21,7 +21,7 @@
*
* @category Twitter
* @package StatusNet
* @author Craig Andrews
* @author Craig Andrews <candrews@integralblue.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
@ -41,7 +41,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
*
* @category Twitter
* @package StatusNet
* @author Craig Andrews
* @author Craig Andrews <candrews@integralblue.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
@ -233,4 +233,97 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
}
}
function membership($args, $apidata)
{
parent::handle($args);
common_debug("in groups api action");
$this->auth_user = $apidata['user'];
$group = $this->get_group($apidata['api_arg'], $apidata);
if (empty($group)) {
$this->clientError('Not Found', 404, $apidata['content-type']);
return;
}
$sitename = common_config('site', 'name');
$title = sprintf(_("Members of %s group"), $group->nickname);
$taguribase = common_config('integration', 'taguri');
$id = "tag:$taguribase:GroupMembership:".$group->id;
$link = common_local_url('showgroup',
array('nickname' => $group->nickname));
$subtitle = sprintf(_('Members of %1$s on %2$s'),
$group->nickname, $sitename);
$page = (int)$this->arg('page', 1);
$count = (int)$this->arg('count', 20);
$max_id = (int)$this->arg('max_id', 0);
$since_id = (int)$this->arg('since_id', 0);
$since = $this->arg('since');
$member = $group->getMembers(($page-1)*$count,
$count, $since_id, $max_id, $since);
switch($apidata['content-type']) {
case 'xml':
$this->show_twitter_xml_users($member);
break;
//TODO implement the RSS and ATOM content types
/*case 'rss':
$this->show_rss_users($member, $title, $link, $subtitle);
break;*/
/*case 'atom':
if (isset($apidata['api_arg'])) {
$selfuri = common_root_url() .
'api/statusnet/groups/membership/' .
$apidata['api_arg'] . '.atom';
} else {
$selfuri = common_root_url() .
'api/statusnet/groups/membership.atom';
}
$this->show_atom_users($member, $title, $id, $link,
$subtitle, null, $selfuri);
break;*/
case 'json':
$this->show_json_users($member);
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
}
}
function is_member($args, $apidata)
{
parent::handle($args);
common_debug("in groups api action");
$this->auth_user = $apidata['user'];
$group = User_group::staticGet($args['group_id']);
if(! $group){
$this->clientError(_('Group not found'), $code = 500);
}
$user = User::staticGet('id', $args['user_id']);
if(! $user){
$this->clientError(_('User not found'), $code = 500);
}
$is_member=$user->isMember($group);
switch($apidata['content-type']) {
case 'xml':
$this->init_document('xml');
$this->element('is_member', null, $is_member);
$this->end_document('xml');
break;
case 'json':
$this->init_document('json');
$this->show_json_objects(array('is_member'=>$is_member));
$this->end_document('json');
break;
default:
$this->clientError(_('API method not found!'), $code = 404);
}
}
}

View File

@ -21,7 +21,7 @@
*
* @category Twitter
* @package StatusNet
* @author Craig Andrews
* @author Craig Andrews <candrews@integralblue.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
@ -41,7 +41,7 @@ require_once INSTALLDIR.'/lib/twitterapi.php';
*
* @category Twitter
* @package StatusNet
* @author Craig Andrews
* @author Craig Andrews <candrews@integralblue.com>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0

View File

@ -165,7 +165,7 @@ class TwittersettingsAction extends ConnectSettingsAction
($flink->noticesync & FOREIGN_NOTICE_RECV) :
false);
$this->elementEnd('li');
} else {
// preserve setting even if bidrection bridge toggled off
if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {

View File

@ -78,14 +78,14 @@ class File extends Memcached_DataObject
$file_id = $x->insert();
if (isset($redir_data['type'])
&& ('text/html' === substr($redir_data['type'], 0, 9))
&& (('text/html' === substr($redir_data['type'], 0, 9) || 'application/xhtml+xml' === substr($redir_data['type'], 0, 21)))
&& ($oembed_data = File_oembed::_getOembed($given_url))) {
File_oembed::saveNew($oembed_data, $file_id);
}
return $x;
}
function processNew($given_url, $notice_id) {
function processNew($given_url, $notice_id=null) {
if (empty($given_url)) return -1; // error, no url to process
$given_url = File_redirection::_canonUrl($given_url);
if (empty($given_url)) return -1; // error, no url to process
@ -96,7 +96,7 @@ class File extends Memcached_DataObject
$redir_data = File_redirection::where($given_url);
$redir_url = $redir_data['url'];
// TODO: max field length
if ($redir_url === $given_url || strlen($redir_url) > 255) {
if ($redir_url === $given_url || strlen($redir_url) > 255) {
$x = File::saveNew($redir_data, $given_url);
$file_id = $x->id;
} else {
@ -119,7 +119,9 @@ class File extends Memcached_DataObject
}
}
File_to_post::processNew($file_id, $notice_id);
if (!empty($notice_id)) {
File_to_post::processNew($file_id, $notice_id);
}
return $x;
}

View File

@ -56,7 +56,7 @@ class Notice extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
/* Notice types */
/* Notice types */
const LOCAL_PUBLIC = 1;
const REMOTE_OMB = 0;
const LOCAL_NONPUBLIC = -1;
@ -260,17 +260,6 @@ class Notice extends Memcached_DataObject
$notice->saveUrls();
// FIXME: why do we have to re-render the content?
// Remove this if it's not necessary.
$orig2 = clone($notice);
$notice->rendered = common_render_content($final, $notice);
if (!$notice->update($orig2)) {
common_log_db_error($notice, 'UPDATE', __FILE__);
return _('Problem saving notice.');
}
$notice->query('COMMIT');
Event::handle('EndNoticeSave', array($notice));

View File

@ -54,7 +54,7 @@ class Status_network extends DB_DataObject
global $config;
$config['db']['database_'.$dbname] = "mysqli://$dbuser:$dbpass@$dbhost/$dbname";
$config['db']['ini_'.$dbname] = INSTALLDIR.'/classes/statusnet.ini';
$config['db']['ini_'.$dbname] = INSTALLDIR.'/classes/status_network.ini';
$config['db']['table_status_network'] = $dbname;
self::$cache = new Memcache();

View File

@ -13,7 +13,7 @@ Bugs
----
If you think you've found a bug in the [StatusNet](http://status.net/) software,
or if there's a new feature you'd like to see, add it into the [StatusNet bug database](http://status.net/PITS/HomePage). Don't forget to check the list of
or if there's a new feature you'd like to see, add it into the [StatusNet bug database](http://status.net/bugs/). Don't forget to check the list of
existing bugs to make sure it hasn't already been reported!
Email

View File

@ -256,7 +256,7 @@ class Services_oEmbed
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (substr($code, 0, 1) != '2') {
throw new Services_oEmbed_Exception('Non-200 code returned');
throw new Services_oEmbed_Exception('Non-200 code returned. Got code ' . $code);
}
curl_close($ch);
@ -302,8 +302,8 @@ class Services_oEmbed
// Find all <link /> tags that have a valid oembed type set. We then
// extract the href attribute for each type.
$regexp = '#<link([^>]*)type="' .
'(application/json|text/xml)\+oembed"([^>]*)>#i';
$regexp = '#<link([^>]*)type[\s\n]*=[\s\n]*"' .
'(application/json|text/xml)\+oembed"([^>]*)>#im';
$m = $ret = array();
if (!preg_match_all($regexp, $body, $m)) {
@ -314,7 +314,7 @@ class Services_oEmbed
foreach ($m[0] as $i => $link) {
$h = array();
if (preg_match('/href="([^"]+)"/i', $link, $h)) {
if (preg_match('/[\s\n]+href[\s\n]*=[\s\n]*"([^"]+)"/im', $link, $h)) {
$ret[$m[2][$i]] = $h[1];
}
}
@ -347,7 +347,7 @@ class Services_oEmbed
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (substr($code, 0, 1) != '2') {
throw new Services_oEmbed_Exception('Non-200 code returned');
throw new Services_oEmbed_Exception('Non-200 code returned. Got code ' . $code);
}
return $result;

View File

@ -454,7 +454,7 @@ class Stomp
*/
public function disconnect ()
{
$header = array();
$headers = array();
if ($this->clientId != null) {
$headers["client-id"] = $this->clientId;

View File

@ -230,7 +230,7 @@ function checkPrereqs()
}
$reqs = array('gd', 'curl',
'xmlwriter', 'mbstring');
'xmlwriter', 'mbstring','tidy');
foreach ($reqs as $req) {
if (!checkExtension($req)) {

View File

@ -114,7 +114,6 @@ class StatsCommand extends Command
class FavCommand extends Command
{
var $other = null;
function __construct($user, $other)
@ -158,6 +157,108 @@ class FavCommand extends Command
$channel->output($this->user, _('Notice marked as fave.'));
}
}
class JoinCommand extends Command
{
var $other = null;
function __construct($user, $other)
{
parent::__construct($user);
$this->other = $other;
}
function execute($channel)
{
$nickname = common_canonical_nickname($this->other);
$group = User_group::staticGet('nickname', $nickname);
$cur = $this->user;
if (!$group) {
$channel->error($cur, _('No such group.'));
return;
}
if ($cur->isMember($group)) {
$channel->error($cur, _('You are already a member of that group'));
return;
}
if (Group_block::isBlocked($group, $cur->getProfile())) {
$channel->error($cur, _('You have been blocked from that group by the admin.'));
return;
}
$member = new Group_member();
$member->group_id = $group->id;
$member->profile_id = $cur->id;
$member->created = common_sql_now();
$result = $member->insert();
if (!$result) {
common_log_db_error($member, 'INSERT', __FILE__);
$channel->error($cur, sprintf(_('Could not join user %s to group %s'),
$cur->nickname, $group->nickname));
return;
}
$channel->output($cur, sprintf(_('%s joined group %s'),
$cur->nickname,
$group->nickname));
}
}
class DropCommand extends Command
{
var $other = null;
function __construct($user, $other)
{
parent::__construct($user);
$this->other = $other;
}
function execute($channel)
{
$nickname = common_canonical_nickname($this->other);
$group = User_group::staticGet('nickname', $nickname);
$cur = $this->user;
if (!$group) {
$channel->error($cur, _('No such group.'));
return;
}
if (!$cur->isMember($group)) {
$channel->error($cur, _('You are not a member of that group.'));
return;
}
$member = new Group_member();
$member->group_id = $group->id;
$member->profile_id = $cur->id;
if (!$member->find(true)) {
$channel->error($cur,_('Could not find membership record.'));
return;
}
$result = $member->delete();
if (!$result) {
common_log_db_error($member, 'INSERT', __FILE__);
$channel->error($cur, sprintf(_('Could not remove user %s to group %s'),
$cur->nickname, $group->nickname));
return;
}
$channel->output($cur, sprintf(_('%s left group %s'),
$cur->nickname,
$group->nickname));
}
}
class WhoisCommand extends Command
@ -396,6 +497,8 @@ class HelpCommand extends Command
"get <nickname> - get last notice from user\n".
"whois <nickname> - get profile info on user\n".
"fav <nickname> - add user's last notice as a 'fave'\n".
"join <group> - join group\n".
"drop <group> - leave group\n".
"stats - get your stats\n".
"stop - same as 'off'\n".
"quit - same as 'off'\n".

View File

@ -70,6 +70,26 @@ class CommandInterpreter
} else {
return new OffCommand($user);
}
case 'join':
if (!$arg) {
return null;
}
list($other, $extra) = explode(' ', $arg, 2);
if ($extra) {
return null;
} else {
return new JoinCommand($user, $other);
}
case 'drop':
if (!$arg) {
return null;
}
list($other, $extra) = explode(' ', $arg, 2);
if ($extra) {
return null;
} else {
return new DropCommand($user, $other);
}
case 'follow':
case 'sub':
if (!$arg) {

View File

@ -213,26 +213,20 @@ class MailboxAction extends CurrentUserDesignAction
}
$this->elementStart('div', 'entry-content');
$this->elementStart('dl', 'timestamp');
$this->element('dt', null, _('Published'));
$this->elementStart('dd', null);
$dt = common_date_iso8601($message->created);
$this->elementStart('a', array('rel' => 'bookmark',
'class' => 'timestamp',
'href' => $messageurl));
$dt = common_date_iso8601($message->created);
$this->element('abbr', array('class' => 'published',
'title' => $dt),
common_date_string($message->created));
$this->elementEnd('a');
$this->elementEnd('dd');
$this->elementEnd('dl');
if ($message->source) {
$this->elementStart('dl', 'device');
$this->elementStart('dt');
$this->text(_('From'));
$this->elementEnd('dt');
$this->showSource($message->source);
$this->elementEnd('dl');
$this->elementStart('span', 'source');
$this->text(_('from'));
$this->element('span', 'device', $this->showSource($message->source));
$this->elementEnd('span');
}
$this->elementEnd('div');
@ -277,18 +271,18 @@ class MailboxAction extends CurrentUserDesignAction
case 'mail':
case 'omb':
case 'api':
$this->element('dd', null, $source_name);
$this->element('span', 'device', $source_name);
break;
default:
$ns = Notice_source::staticGet($source);
if ($ns) {
$this->elementStart('dd', null);
$this->elementStart('span', 'device');
$this->element('a', array('href' => $ns->url,
'rel' => 'external'),
$ns->name);
$this->elementEnd('dd');
'rel' => 'external'),
$ns->name);
$this->elementEnd('span');
} else {
$this->element('dd', null, $source_name);
$this->out->element('span', 'device', $source_name);
}
break;
}

View File

@ -22,7 +22,7 @@
* @category Action
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2008 StatusNet, Inc.
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/

View File

@ -154,83 +154,134 @@ function broadcast_twitter($notice)
TWITTER_SERVICE);
if (is_twitter_bound($notice, $flink)) {
$user = $flink->getUser();
// XXX: Hack to get around PHP cURL's use of @ being a a meta character
$statustxt = preg_replace('/^@/', ' @', $notice->content);
// Convert !groups to #hashes
$statustxt = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/', "\\1#\\2", $statustxt);
$token = TwitterOAuthClient::unpackToken($flink->credentials);
$client = new TwitterOAuthClient($token->key, $token->secret);
$status = null;
try {
$status = $client->statusesUpdate($statustxt);
} catch (OAuthClientCurlException $e) {
if ($e->getMessage() == 'The requested URL returned error: 401') {
$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 auth token! We need to delete the foreign_link
// to Twitter and inform the user.
remove_twitter_link($flink);
return true;
} else {
// 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;
}
if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
return broadcast_oauth($notice, $flink);
} else {
return broadcast_basicauth($notice, $flink);
}
}
if (empty($status)) {
return true;
}
// This could represent a failure posting,
// or the Twitter API might just be behaving flakey.
function broadcast_oauth($notice, $flink) {
$user = $flink->getUser();
$statustxt = format_status($notice);
// Convert !groups to #hashes
$statustxt = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/', "\\1#\\2", $statustxt);
$token = TwitterOAuthClient::unpackToken($flink->credentials);
$client = new TwitterOAuthClient($token->key, $token->secret);
$status = null;
try {
$status = $client->statusesUpdate($statustxt);
} catch (OAuthClientCurlException $e) {
return process_error($e, $flink);
}
if (empty($status)) {
// This could represent a failure posting,
// or the Twitter API might just be behaving flakey.
$errmsg = sprintf('Twitter bridge - 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;
}
// Notice crossed the great divide
$msg = sprintf('Twitter bridge - posted notice %s to Twitter using OAuth.',
$notice->id);
common_log(LOG_INFO, $msg);
return true;
}
function broadcast_basicauth($notice, $flink)
{
$user = $flink->getUser();
$statustxt = format_status($notice);
$client = new TwitterBasicAuthClient($flink);
$status = null;
try {
$status = $client->statusesUpdate($statustxt);
} catch (BasicAuthCurlException $e) {
return process_error($e, $flink);
}
if (empty($status)) {
$errmsg = sprintf('Twitter bridge - 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);
$errmsg = sprintf('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;
}
// Notice crossed the great divide
$msg = sprintf('Twitter bridge posted notice %s to Twitter.',
$notice->id);
common_log(LOG_INFO, $msg);
return false;
}
$msg = sprintf('Twitter bridge - posted notice %s to Twitter using basic auth.',
$notice->id);
common_log(LOG_INFO, $msg);
return true;
}
function process_error($e, $flink)
{
$user = $flink->getUser();
$errmsg = $e->getMessage();
$delivered = false;
switch($errmsg) {
case 'The requested URL returned error: 401':
$logmsg = sprintf('Twiter bridge - User %1$s (user id: %2$s) has an invalid ' .
'Twitter screen_name/password combo or an invalid acesss token.',
$user->nickname, $user->id);
$delivered = true;
remove_twitter_link($flink);
break;
case 'The requested URL returned error: 403':
$logmsg = sprintf('Twitter bridge - User %1$s (user id: %2$s) has exceeded ' .
'his/her Twitter request limit.',
$user->nickname, $user->id);
break;
default:
$logmsg = sprintf('Twitter bridge - 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());
break;
}
common_log(LOG_WARNING, $logmsg);
return $delivered;
}
function format_status($notice)
{
// XXX: Hack to get around PHP cURL's use of @ being a a meta character
return preg_replace('/^@/', ' @', $notice->content);
}
function remove_twitter_link($flink)
{
$user = $flink->getUser();
common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
"user $user->nickname (user id: $user->id).");
"user $user->nickname (user id: $user->id).");
$result = $flink->delete();

View File

@ -788,6 +788,52 @@ class TwitterapiAction extends Action
$this->end_document('xml');
}
function show_twitter_xml_users($user)
{
$this->init_document('xml');
$this->elementStart('users', array('type' => 'array'));
if (is_array($user)) {
foreach ($group as $g) {
$twitter_user = $this->twitter_user_array($g);
$this->show_twitter_xml_user($twitter_user,'user');
}
} else {
while ($user->fetch()) {
$twitter_user = $this->twitter_user_array($user);
$this->show_twitter_xml_user($twitter_user);
}
}
$this->elementEnd('users');
$this->end_document('xml');
}
function show_json_users($user)
{
$this->init_document('json');
$users = array();
if (is_array($user)) {
foreach ($user as $u) {
$twitter_user = $this->twitter_user_array($u);
array_push($users, $twitter_user);
}
} else {
while ($user->fetch()) {
$twitter_user = $this->twitter_user_array($user);
array_push($users, $twitter_user);
}
}
$this->show_json_objects($users);
$this->end_document('json');
}
function show_single_json_group($group)
{
$this->init_document('json');

View File

@ -0,0 +1,236 @@
<?php
/**
* StatusNet, 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 StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Exception wrapper for cURL errors
*
* @category Integration
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
*/
class BasicAuthCurlException extends Exception
{
}
/**
* Class for talking to the Twitter API with HTTP Basic Auth.
*
* @category Integration
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
*/
class TwitterBasicAuthClient
{
var $screen_name = null;
var $password = null;
/**
* constructor
*
* @param Foreign_link $flink a Foreign_link storing the
* Twitter user's password, etc.
*/
function __construct($flink)
{
$fuser = $flink->getForeignUser();
$this->screen_name = $fuser->nickname;
$this->password = $flink->credentials;
}
/**
* Calls Twitter's /statuses/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,
'source' => common_config('integration', 'source'),
'in_reply_to_status_id' => $in_reply_to_status_id);
$response = $this->httpRequest($url, $params);
$status = json_decode($response);
return $status;
}
/**
* Calls Twitter's /statuses/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->httpRequest($url);
$statuses = json_decode($response);
return $statuses;
}
/**
* Calls Twitter's /statuses/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->httpRequest($url);
$friends = json_decode($response);
return $friends;
}
/**
* Calls Twitter's /statuses/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->httpRequest($url);
$ids = json_decode($response);
return $ids;
}
/**
* Make a HTTP request using cURL.
*
* @param string $url Where to make the request
* @param array $params post parameters
*
* @return mixed the request
*/
function httpRequest($url, $params = null, $auth = true)
{
$options = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FAILONERROR => true,
CURLOPT_HEADER => false,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => 'StatusNet',
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;
}
if ($auth) {
$options[CURLOPT_USERPWD] = $this->screen_name .
':' . $this->password;
}
$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 BasicAuthCurlException($msg, $code);
}
curl_close($ch);
return $response;
}
}

View File

@ -22,7 +22,7 @@
* @category Integration
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2008 StatusNet, Inc.
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
@ -81,6 +81,15 @@ class TwitterOAuthClient extends OAuthClient
return new OAuthToken($vals[0], $vals[1]);
}
static function isPackedToken($str)
{
if (strpos($str, chr(0)) === false) {
return false;
} else {
return true;
}
}
/**
* Builds a link to Twitter's endpoint for authorizing a request token
*

View File

@ -413,7 +413,7 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
// Start off with a regex
$regex = '#'.
'(?:^|[\s\(\)\[\]\{\}\\\'\\\";]+)(?![\@\!\#])'.
'(?P<url>'.
'('.
'(?:'.
'(?:'. //Known protocols
'(?:'.
@ -421,7 +421,7 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
'|'.
'(?:(?:mailto|aim|tel|xmpp):)'.
')'.
'(?:[\pN\pL\-\_\+]+(?::[\pN\pL\-\_\+]+)?\@)?'. //user:pass@
'(?:[\pN\pL\-\_\+\%\~]+(?::[\pN\pL\-\_\+\%\~]+)?\@)?'. //user:pass@
'(?:'.
'(?:'.
'\[[\pN\pL\-\_\:\.]+(?<![\.\:])\]'. //[dns]
@ -434,7 +434,7 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
'|(?:'. //IPv6
'\[?(?:(?:(?:[0-9A-Fa-f]{1,4}:){7}(?:(?:[0-9A-Fa-f]{1,4})|:))|(?:(?:[0-9A-Fa-f]{1,4}:){6}(?::|(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})|(?::[0-9A-Fa-f]{1,4})))|(?:(?:[0-9A-Fa-f]{1,4}:){5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){4}(?::[0-9A-Fa-f]{1,4}){0,1}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){3}(?::[0-9A-Fa-f]{1,4}){0,2}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:){2}(?::[0-9A-Fa-f]{1,4}){0,3}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:[0-9A-Fa-f]{1,4}:)(?::[0-9A-Fa-f]{1,4}){0,4}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?::(?::[0-9A-Fa-f]{1,4}){0,5}(?:(?::(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})?)|(?:(?::[0-9A-Fa-f]{1,4}){1,2})))|(?:(?:(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})(?:\.(?:25[0-5]|2[0-4]\d|[01]?\d{1,2})){3})))\]?'.
')|(?:'. //DNS
'(?:[\pN\pL\-\_\+]+(?:\:[\pN\pL\-\_\+]+)?\@)?'. //user:pass@
'(?:[\pN\pL\-\_\+\%\~]+(?:\:[\pN\pL\-\_\+\%\~]+)?\@)?'. //user:pass@
'[\pN\pL\-\_]+(?:\.[\pN\pL\-\_]+)*\.'.
//tld list from http://data.iana.org/TLD/tlds-alpha-by-domain.txt, also added local, loc, and onion
'(?:AC|AD|AE|AERO|AF|AG|AI|AL|AM|AN|AO|AQ|AR|ARPA|AS|ASIA|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BIZ|BJ|BM|BN|BO|BR|BS|BT|BV|BW|BY|BZ|CA|CAT|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|COM|COOP|CR|CU|CV|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EDU|EE|EG|ER|ES|ET|EU|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GOV|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|INFO|INT|IO|IQ|IR|IS|IT|JE|JM|JO|JOBS|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MG|MH|MIL|MK|ML|MM|MN|MO|MOBI|MP|MQ|MR|MS|MT|MU|MUSEUM|MV|MW|MX|MY|MZ|NA|NAME|NC|NE|NET|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|ORG|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PRO|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|ST|SU|SV|SY|SZ|TC|TD|TEL|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TP|TR|TRAVEL|TT|TV|TW|TZ|UA|UG|UK|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|XN--0ZWM56D|测试|XN--11B5BS3A9AJ6G|परीक्षा|XN--80AKHBYKNJ4F|испытание|XN--9T4B11YI5A|테스트|XN--DEBA0AD|טעסט|XN--G6W251D|測試|XN--HGBK6AJ7F53BBA|آزمایشی|XN--HLCJ6AYA9ESC7A|பரிட்சை|XN--JXALPDLP|δοκιμή|XN--KGBECHTV|إختبار|XN--ZCKZAH|テスト|YE|YT|YU|ZA|ZM|ZW|local|loc|onion)'.
@ -442,22 +442,22 @@ function common_replace_urls_callback($text, $callback, $notice_id = null) {
')'.
'(?:'.
'(?:\:\d+)?'. //:port
'(?:/[\pN\pL$\[\]\,\!\(\)\.\-\_\+\/\=\&\;]*)?'. // /path
'(?:\?[\pN\pL\$\[\]\,\!\(\)\.\-\_\+\/\=\&\;\/]*)?'. // ?query string
'(?:\#[\pN\pL$\[\]\,\!\(\)\.\-\_\+\/\=\&\;\/\?\#]*)?'. // #fragment
'(?:/[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~]*)?'. // /path
'(?:\?[\pN\pL\$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\/]*)?'. // ?query string
'(?:\#[\pN\pL$\[\]\,\!\(\)\.\:\-\_\+\/\=\&\;\%\~\/\?\#]*)?'. // #fragment
')(?<![\?\.\,\#\,])'.
')'.
'#ixu';
preg_match_all($regex,$text,$matches);
//print_r($matches);
return preg_replace_callback($regex, curry(callback_helper,$callback,$notice_id) ,$text);
return preg_replace_callback($regex, curry('callback_helper',$callback,$notice_id) ,$text);
}
function callback_helper($matches, $callback, $notice_id) {
$url=$matches['url'];
$url=$matches[1];
$left = strpos($matches[0],$url);
$right = $left+strlen($url);
$groupSymbolSets=array(
array(
'left'=>'(',
@ -491,13 +491,11 @@ function callback_helper($matches, $callback, $notice_id) {
$url=substr($url,0,-1);
}
}while($original_url!=$url);
if(empty($notice_id)){
$result = call_user_func_array($callback,$url);
}else{
$result = call_user_func_array($callback, array($url,$notice_id) );
$result = call_user_func_array($callback, array(array($url,$notice_id)) );
}
return substr($matches[0],0,$left) . $result . substr($matches[0],$right);
}
@ -508,16 +506,13 @@ function curry($fn) {
array_shift($args);
$id = uniqid('_partial');
$GLOBALS[$id] = array($fn, $args);
return create_function(
'',
'
$args = func_get_args();
return call_user_func_array(
$GLOBALS["'.$id.'"][0],
array_merge(
$args,
$GLOBALS["'.$id.'"][1]));
');
return create_function('',
'$args = func_get_args(); '.
'return call_user_func_array('.
'$GLOBALS["'.$id.'"][0],'.
'array_merge('.
'$args,'.
'$GLOBALS["'.$id.'"][1]));');
}
function common_linkify($url) {
@ -525,7 +520,7 @@ function common_linkify($url) {
// functions
$url = htmlspecialchars_decode($url);
if(strpos($url, '@')!==false && strpos($url, ':')===false){
if(strpos($url, '@') !== false && strpos($url, ':') === false) {
//url is an email address without the mailto: protocol
return XMLStringer::estring('a', array('href' => "mailto:$url", 'rel' => 'external'), $url);
}
@ -547,42 +542,30 @@ function common_linkify($url) {
$attachment_id = null;
$has_thumb = false;
// Check to see whether there's a filename associated with this URL.
// If there is, it's an upload and qualifies as an attachment
// Check to see whether this is a known "attachment" URL.
$localfile = File::staticGet('url', $longurl);
$f = File::staticGet('url', $longurl);
if (!empty($localfile)) {
if (isset($localfile->filename)) {
$is_attachment = true;
$attachment_id = $localfile->id;
}
if (empty($f)) {
// XXX: this writes to the database. :<
$f = File::processNew($longurl);
}
// if this URL is an attachment, then we set class='attachment' and id='attahcment-ID'
// where ID is the id of the attachment for the given URL.
//
// we need a better test telling what can be shown as an attachment
// we're currently picking up oembeds only.
// I think the best option is another file_view table in the db
// and associated dbobject.
if (!empty($f)) {
if (isset($f->filename)) {
$is_attachment = true;
$attachment_id = $f->id;
} else { // if it has OEmbed info, it's an attachment, too
$foe = File_oembed::staticGet('file_id', $f->id);
if (!empty($foe)) {
$is_attachment = true;
$attachment_id = $f->id;
$query = "select file_oembed.file_id as file_id from file join file_oembed on file.id = file_oembed.file_id where file.url='$longurl'";
$file = new File;
$file->query($query);
$file->fetch();
if (!empty($file->file_id)) {
$is_attachment = true;
$attachment_id = $file->file_id;
$query = "select file_thumbnail.file_id as file_id from file join file_thumbnail on file.id = file_thumbnail.file_id where file.url='$longurl'";
$file2 = new File;
$file2->query($query);
$file2->fetch();
if (!empty($file2)) {
$has_thumb = true;
$thumb = File_thumbnail::staticGet('file_id', $f->id);
if (!empty($thumb)) {
$has_thumb = true;
}
}
}
}

View File

@ -142,13 +142,20 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon
{
$friends = array();
$token = TwitterOAuthClient::unpackToken($flink->credentials);
$client = null;
$client = new TwitterOAuthClient($token->key, $token->secret);
if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
$token = TwitterOAuthClient::unpackToken($flink->credentials);
$client = new TwitterOAuthClient($token->key, $token->secret);
common_debug($this->name() . '- Grabbing friends IDs with OAuth.');
} else {
$client = new TwitterBasicAuthClient($flink);
common_debug($this->name() . '- Grabbing friends IDs with basic auth.');
}
try {
$friends_ids = $client->friendsIds();
} catch (OAuthCurlException $e) {
} catch (Exception $e) {
common_log(LOG_WARNING, $this->name() .
' - cURL error getting friend ids ' .
$e->getCode() . ' - ' . $e->getMessage());
@ -177,7 +184,7 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon
try {
$more_friends = $client->statusesFriends(null, null, null, $i);
} catch (OAuthCurlException $e) {
} catch (Exception $e) {
common_log(LOG_WARNING, $this->name() .
' - cURL error getting Twitter statuses/friends ' .
"page $i - " . $e->getCode() . ' - ' .

View File

@ -148,9 +148,9 @@ class TwitterStatusFetcher extends ParallelizingDaemon
function getTimeline($flink)
{
if (empty($flink)) {
if (empty($flink)) {
common_log(LOG_WARNING, $this->name() .
" - Can't retrieve Foreign_link for foreign ID $fid");
" - Can't retrieve Foreign_link for foreign ID $fid");
return;
}
@ -161,17 +161,24 @@ class TwitterStatusFetcher extends ParallelizingDaemon
// to start importing? How many statuses? Right now I'm going
// with the default last 20.
$token = TwitterOAuthClient::unpackToken($flink->credentials);
$client = null;
$client = new TwitterOAuthClient($token->key, $token->secret);
if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
$token = TwitterOAuthClient::unpackToken($flink->credentials);
$client = new TwitterOAuthClient($token->key, $token->secret);
common_debug($this->name() . ' - Grabbing friends timeline with OAuth.');
} else {
$client = new TwitterBasicAuthClient($flink);
common_debug($this->name() . ' - Grabbing friends timeline with basic auth.');
}
$timeline = null;
try {
$timeline = $client->statusesFriendsTimeline();
} catch (OAuthClientCurlException $e) {
} catch (Exception $e) {
common_log(LOG_WARNING, $this->name() .
' - OAuth client unable to get friends timeline for user ' .
' - Twitter client unable to get friends timeline for user ' .
$flink->user_id . ' - code: ' .
$e->getCode() . 'msg: ' . $e->getMessage());
}

View File

@ -31,8 +31,12 @@ class URLDetectionTest extends PHPUnit_Framework_TestCase
'<a href="http://127.0.0.1/" rel="external">127.0.0.1</a>'),
array('127.0.0.1:99',
'<a href="http://127.0.0.1:99/" rel="external">127.0.0.1:99</a>'),
array('127.0.0.1/test.php',
'<a href="http://127.0.0.1/test.php" rel="external">127.0.0.1/test.php</a>'),
array('127.0.0.1/Name:test.php',
'<a href="http://127.0.0.1/Name:test.php" rel="external">127.0.0.1/Name:test.php</a>'),
array('127.0.0.1/~test',
'<a href="http://127.0.0.1/~test" rel="external">127.0.0.1/~test</a>'),
array('127.0.0.1/test%20stuff',
'<a href="http://127.0.0.1/test%20stuff" rel="external">127.0.0.1/test%20stuff</a>'),
array('http://[::1]:99/test.php',
'<a href="http://[::1]:99/test.php" rel="external">http://[::1]:99/test.php</a>'),
array('http://::1/test.php',