summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/DisqusPlugin.php2
-rw-r--r--plugins/OStatus/OStatusPlugin.php32
-rw-r--r--plugins/OStatus/actions/ostatusgroup.php2
-rw-r--r--plugins/OStatus/actions/ostatusinit.php14
-rw-r--r--plugins/OStatus/actions/ostatussub.php14
-rw-r--r--plugins/OStatus/actions/ownerxrd.php4
-rw-r--r--plugins/OStatus/actions/pushcallback.php15
-rw-r--r--plugins/OStatus/actions/pushhub.php11
-rw-r--r--plugins/OStatus/actions/userxrd.php2
-rw-r--r--plugins/OStatus/classes/FeedSub.php5
-rw-r--r--plugins/OStatus/classes/HubSub.php6
-rw-r--r--plugins/OStatus/classes/Magicsig.php26
-rw-r--r--plugins/OStatus/classes/Ostatus_profile.php54
-rw-r--r--plugins/OStatus/lib/discovery.php5
-rw-r--r--plugins/OStatus/lib/hubconfqueuehandler.php1
-rw-r--r--plugins/OStatus/lib/magicenvelope.php23
-rw-r--r--plugins/OStatus/lib/ostatusqueuehandler.php1
-rw-r--r--plugins/OStatus/lib/salmon.php11
-rw-r--r--plugins/OStatus/lib/salmonaction.php2
-rw-r--r--plugins/OStatus/lib/salmonqueuehandler.php2
-rw-r--r--plugins/OStatus/lib/xrd.php23
-rw-r--r--plugins/OStatus/lib/xrdaction.php5
-rw-r--r--plugins/OStatus/scripts/fixup-shadow.php3
-rw-r--r--plugins/OStatus/scripts/testfeed.php1
-rw-r--r--plugins/OStatus/scripts/updateostatus.php2
-rw-r--r--plugins/OStatus/tests/FeedDiscoveryTest.php2
-rw-r--r--plugins/OStatus/tests/remote-tests.php3
-rw-r--r--plugins/TwitterBridge/Notice_to_status.php180
-rw-r--r--plugins/TwitterBridge/TwitterBridgePlugin.php192
-rw-r--r--plugins/TwitterBridge/Twitter_synch_status.php202
-rwxr-xr-xplugins/TwitterBridge/daemons/synctwitterfriends.php5
-rwxr-xr-xplugins/TwitterBridge/daemons/twitterstatusfetcher.php240
-rw-r--r--plugins/TwitterBridge/twitter.php112
-rw-r--r--plugins/TwitterBridge/twitterbasicauthclient.php229
-rw-r--r--plugins/TwitterBridge/twitteroauthclient.php141
35 files changed, 1103 insertions, 469 deletions
diff --git a/plugins/DisqusPlugin.php b/plugins/DisqusPlugin.php
index dc56f320c..c07eaaabd 100644
--- a/plugins/DisqusPlugin.php
+++ b/plugins/DisqusPlugin.php
@@ -148,7 +148,7 @@ ENDOFSCRIPT;
$noticeUrl .= '#disqus_thread';
$noticeListItem->out->element(
- 'a', array('href' => $noticeUrl, 'class' => 'disqus_count', 'Comments')
+ 'a', array('href' => $noticeUrl, 'class' => 'disqus_count'), 'Comments'
);
$noticeListItem->showNoticeOptions();
diff --git a/plugins/OStatus/OStatusPlugin.php b/plugins/OStatus/OStatusPlugin.php
index f9a8782fa..77bc9872b 100644
--- a/plugins/OStatus/OStatusPlugin.php
+++ b/plugins/OStatus/OStatusPlugin.php
@@ -564,7 +564,9 @@ class OStatusPlugin extends Plugin
$act->time = time();
$act->title = _("Follow");
- $act->content = sprintf(_("%s is now following %s."),
+ // TRANS: Success message for subscribe to user attempt through OStatus.
+ // TRANS: %1$s is the subscriber name, %2$s is the subscribed user's name.
+ $act->content = sprintf(_("%1$s is now following %2$s."),
$subscriber->getBestName(),
$other->getBestName());
@@ -612,7 +614,9 @@ class OStatusPlugin extends Plugin
$act->time = time();
$act->title = _("Unfollow");
- $act->content = sprintf(_("%s stopped following %s."),
+ // TRANS: Success message for unsubscribe from user attempt through OStatus.
+ // TRANS: %1$s is the unsubscriber's name, %2$s is the unsubscribed user's name.
+ $act->content = sprintf(_("%1$s stopped following %2$s."),
$profile->getBestName(),
$other->getBestName());
@@ -657,7 +661,9 @@ class OStatusPlugin extends Plugin
$act->time = time();
$act->title = _m("Join");
- $act->content = sprintf(_m("%s has joined group %s."),
+ // TRANS: Success message for subscribe to group attempt through OStatus.
+ // TRANS: %1$s is the member name, %2$s is the subscribed group's name.
+ $act->content = sprintf(_m("%1$s has joined group %2$s."),
$member->getBestName(),
$oprofile->getBestName());
@@ -706,7 +712,9 @@ class OStatusPlugin extends Plugin
$act->time = time();
$act->title = _m("Leave");
- $act->content = sprintf(_m("%s has left group %s."),
+ // TRANS: Success message for unsubscribe from group attempt through OStatus.
+ // TRANS: %1$s is the member name, %2$s is the unsubscribed group's name.
+ $act->content = sprintf(_m("%1$s has left group %2$s."),
$member->getBestName(),
$oprofile->getBestName());
@@ -746,7 +754,9 @@ class OStatusPlugin extends Plugin
$act->time = time();
$act->title = _("Favor");
- $act->content = sprintf(_("%s marked notice %s as a favorite."),
+ // TRANS: Success message for adding a favorite notice through OStatus.
+ // TRANS: %1$s is the favoring user's name, %2$s is URI to the favored notice.
+ $act->content = sprintf(_("%1$s marked notice %2$s as a favorite."),
$profile->getBestName(),
$notice->uri);
@@ -790,7 +800,9 @@ class OStatusPlugin extends Plugin
common_date_iso8601(time()));
$act->time = time();
$act->title = _("Disfavor");
- $act->content = sprintf(_("%s marked notice %s as no longer a favorite."),
+ // TRANS: Success message for remove a favorite notice through OStatus.
+ // TRANS: %1$s is the unfavoring user's name, %2$s is URI to the no longer favored notice.
+ $act->content = sprintf(_("%1$s marked notice %2$s as no longer a favorite."),
$profile->getBestName(),
$notice->uri);
@@ -865,7 +877,7 @@ class OStatusPlugin extends Plugin
'class' => 'entity_subscribe'));
$action->element('a', array('href' => common_local_url($target),
'class' => 'entity_remote_subscribe')
- , _m('Remote'));
+ , _m('Remote')); // @todo: i18n: Add translator hint for this text.
$action->elementEnd('p');
$action->elementEnd('div');
}
@@ -905,6 +917,8 @@ class OStatusPlugin extends Plugin
common_date_iso8601(time()));
$act->time = time();
$act->title = _m("Profile update");
+ // TRANS: Ping text for remote profile update through OStatus.
+ // TRANS: %s is user that updated their profile.
$act->content = sprintf(_m("%s has updated their profile page."),
$profile->getBestName());
@@ -934,7 +948,7 @@ class OStatusPlugin extends Plugin
array('nickname' => $profileUser->nickname));
$output->element('a', array('href' => $url,
'class' => 'entity_remote_subscribe'),
- _m('Subscribe'));
+ _m('Subscribe')); // @todo: i18n: Add context.
$output->elementEnd('li');
}
}
@@ -950,7 +964,7 @@ class OStatusPlugin extends Plugin
'homepage' => 'http://status.net/wiki/Plugin:OStatus',
'rawdescription' =>
_m('Follow people across social networks that implement '.
- '<a href="http://ostatus.org/">OStatus</a>.'));
+ '<a href="http://ostatus.org/">OStatus</a>.')); // @todo i18n: Add translator hint.
return true;
}
diff --git a/plugins/OStatus/actions/ostatusgroup.php b/plugins/OStatus/actions/ostatusgroup.php
index 1b368de63..1861e866f 100644
--- a/plugins/OStatus/actions/ostatusgroup.php
+++ b/plugins/OStatus/actions/ostatusgroup.php
@@ -74,7 +74,7 @@ class OStatusGroupAction extends OStatusSubAction
$this->input('profile',
_m('Join group'),
$this->profile_uri,
- _m("OStatus group's address, like http://example.net/group/nickname"));
+ _m("OStatus group's address, like http://example.net/group/nickname."));
$this->elementEnd('li');
$this->elementEnd('ul');
diff --git a/plugins/OStatus/actions/ostatusinit.php b/plugins/OStatus/actions/ostatusinit.php
index 22aea9f70..0c991aba9 100644
--- a/plugins/OStatus/actions/ostatusinit.php
+++ b/plugins/OStatus/actions/ostatusinit.php
@@ -45,13 +45,13 @@ class OStatusInitAction extends Action
// Local user or group the remote wants to subscribe to
$this->nickname = $this->trimmed('nickname');
$this->group = $this->trimmed('group');
-
+
// Webfinger or profile URL of the remote user
$this->profile = $this->trimmed('profile');
return true;
}
-
+
function handle($args)
{
parent::handle($args);
@@ -69,7 +69,7 @@ class OStatusInitAction extends Action
$this->showForm();
}
}
-
+
function showForm($err = null)
{
$this->err = $err;
@@ -109,12 +109,12 @@ class OStatusInitAction extends Action
$this->elementStart('ul', 'form_data');
$this->elementStart('li', array('id' => 'ostatus_nickname'));
$this->input('nickname', _m('User nickname'), $this->nickname,
- _m('Nickname of the user you want to follow'));
+ _m('Nickname of the user you want to follow.'));
$this->hidden('group', $this->group); // pass-through for magic links
$this->elementEnd('li');
$this->elementStart('li', array('id' => 'ostatus_profile'));
$this->input('profile', _m('Profile Account'), $this->profile,
- _m('Your account id (i.e. user@identi.ca)'));
+ _m('Your account id (e.g. user@identi.ca).'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('submit', $submit);
@@ -199,7 +199,7 @@ class OStatusInitAction extends Action
function title()
{
- return _m('OStatus Connect');
+ return _m('OStatus Connect');
}
-
+
}
diff --git a/plugins/OStatus/actions/ostatussub.php b/plugins/OStatus/actions/ostatussub.php
index 28714514f..4cbd7d034 100644
--- a/plugins/OStatus/actions/ostatussub.php
+++ b/plugins/OStatus/actions/ostatussub.php
@@ -64,11 +64,11 @@ class OStatusSubAction extends Action
$this->input('profile',
_m('Subscribe to'),
$this->profile_uri,
- _m("OStatus user's address, like nickname@example.com or http://example.net/nickname"));
+ _m("OStatus user's address, like nickname@example.com or http://example.net/nickname")); // @todo i18n FIXME: needs context/translator hint.
$this->elementEnd('li');
$this->elementEnd('ul');
- $this->submit('validate', _m('Continue'));
+ $this->submit('validate', _m('Continue')); // @todo i18n FIXME: needs context/translator hint.
$this->elementEnd('fieldset');
@@ -103,10 +103,10 @@ class OStatusSubAction extends Action
$this->hidden('profile', $this->profile_uri);
if ($this->oprofile->isGroup()) {
$this->submit('submit', _m('Join'), 'submit', null,
- _m('Join this group'));
+ _m('Join this group')); // @todo i18n FIXME: needs context/translator hint.
} else {
$this->submit('submit', _m('Confirm'), 'submit', null,
- _m('Subscribe to this user'));
+ _m('Subscribe to this user')); // @todo i18n FIXME: needs context/translator hint.
}
$this->elementEnd('fieldset');
$this->elementEnd('form');
@@ -244,13 +244,13 @@ class OStatusSubAction extends Action
} else if (Validate::uri($this->profile_uri)) {
$this->oprofile = Ostatus_profile::ensureProfileURL($this->profile_uri);
} else {
- $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
+ $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname.");
common_debug('Invalid address format.', __FILE__);
return false;
}
return true;
} catch (FeedSubBadURLException $e) {
- $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
+ $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname.");
common_debug('Invalid URL or could not reach server.', __FILE__);
} catch (FeedSubBadResponseException $e) {
$this->error = _m("Sorry, we could not reach that feed. Please try that OStatus address again later.");
@@ -269,7 +269,7 @@ class OStatusSubAction extends Action
common_debug('Not a recognized feed type.', __FILE__);
} catch (Exception $e) {
// Any new ones we forgot about
- $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname");
+ $this->error = _m("Sorry, we could not reach that address. Please make sure that the OStatus address is like nickname@example.com or http://example.net/nickname.");
common_debug(sprintf('Bad feed URL: %s %s', get_class($e), $e->getMessage()), __FILE__);
}
diff --git a/plugins/OStatus/actions/ownerxrd.php b/plugins/OStatus/actions/ownerxrd.php
index 9c141d8c7..3fcb982b8 100644
--- a/plugins/OStatus/actions/ownerxrd.php
+++ b/plugins/OStatus/actions/ownerxrd.php
@@ -32,7 +32,7 @@ class OwnerxrdAction extends XrdAction
function prepare($args)
{
$this->user = User::siteOwner();
-
+
if (!$this->user) {
$this->clientError(_('No such user.'), 404);
return false;
@@ -40,7 +40,7 @@ class OwnerxrdAction extends XrdAction
$nick = common_canonical_nickname($this->user->nickname);
$acct = 'acct:' . $nick . '@' . common_config('site', 'server');
-
+
$this->xrd = new XRD();
// Check to see if a $config['webfinger']['owner'] has been set
diff --git a/plugins/OStatus/actions/pushcallback.php b/plugins/OStatus/actions/pushcallback.php
index 9a2067b8c..6c6978745 100644
--- a/plugins/OStatus/actions/pushcallback.php
+++ b/plugins/OStatus/actions/pushcallback.php
@@ -37,7 +37,7 @@ class PushCallbackAction extends Action
$this->handleGet();
}
}
-
+
/**
* Handler for POST content updates from the hub
*/
@@ -46,11 +46,12 @@ class PushCallbackAction extends Action
$feedid = $this->arg('feed');
common_log(LOG_INFO, "POST for feed id $feedid");
if (!$feedid) {
- throw new ServerException('Empty or invalid feed id', 400);
+ throw new ServerException('Empty or invalid feed id.', 400);
}
$feedsub = FeedSub::staticGet('id', $feedid);
if (!$feedsub) {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ServerException('Unknown PuSH feed id ' . $feedid, 400);
}
@@ -70,7 +71,7 @@ class PushCallbackAction extends Action
$qm = QueueManager::get();
$qm->enqueue($data, 'pushin');
}
-
+
/**
* Handler for GET verification requests from the hub.
*/
@@ -88,20 +89,24 @@ class PushCallbackAction extends Action
$feedsub = FeedSub::staticGet('uri', $topic);
if (!$feedsub) {
- throw new ClientException("Bad hub.topic feed $topic", 404);
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
+ throw new ClientException("Bad hub.topic feed $topic.", 404);
}
if ($feedsub->verify_token !== $verify_token) {
- throw new ClientException("Bad hub.verify_token $token for $topic", 404);
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
+ throw new ClientException("Bad hub.verify_token $token for $topic.", 404);
}
if ($mode == 'subscribe') {
// We may get re-sub requests legitimately.
if ($feedsub->sub_state != 'subscribe' && $feedsub->sub_state != 'active') {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Unexpected subscribe request for $topic.", 404);
}
} else {
if ($feedsub->sub_state != 'unsubscribe') {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Unexpected unsubscribe request for $topic.", 404);
}
}
diff --git a/plugins/OStatus/actions/pushhub.php b/plugins/OStatus/actions/pushhub.php
index 842d65e7d..6909b8539 100644
--- a/plugins/OStatus/actions/pushhub.php
+++ b/plugins/OStatus/actions/pushhub.php
@@ -36,7 +36,6 @@ Things to consider...
*/
-
class PushHubAction extends Action
{
function arg($arg, $def=null)
@@ -63,8 +62,10 @@ class PushHubAction extends Action
$this->subunsub($mode);
break;
case "publish":
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Publishing outside feeds not supported.", 400);
default:
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Unrecognized mode '$mode'.", 400);
}
}
@@ -84,16 +85,19 @@ class PushHubAction extends Action
$topic = $this->argUrl('hub.topic');
if (!$this->recognizedFeed($topic)) {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Unsupported hub.topic $topic; this hub only serves local user and group Atom feeds.");
}
$verify = $this->arg('hub.verify'); // @fixme may be multiple
if ($verify != 'sync' && $verify != 'async') {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Invalid hub.verify $verify; must be sync or async.");
}
$lease = $this->arg('hub.lease_seconds', null);
if ($mode == 'subscribe' && $lease != '' && !preg_match('/^\d+$/', $lease)) {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Invalid hub.lease $lease; must be empty or positive integer.");
}
@@ -101,6 +105,7 @@ class PushHubAction extends Action
$secret = $this->arg('hub.secret', null);
if ($secret != '' && strlen($secret) >= 200) {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Invalid hub.secret $secret; must be under 200 bytes.");
}
@@ -152,6 +157,7 @@ class PushHubAction extends Action
if ($feed == $userFeed) {
$user = User::staticGet('id', $id);
if (!$user) {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Invalid hub.topic $feed; user doesn't exist.");
} else {
return true;
@@ -160,6 +166,7 @@ class PushHubAction extends Action
if ($feed == $groupFeed) {
$user = User_group::staticGet('id', $id);
if (!$user) {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Invalid hub.topic $feed; group doesn't exist.");
} else {
return true;
@@ -183,6 +190,7 @@ class PushHubAction extends Action
if (Validate::uri($url, $params)) {
return $url;
} else {
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
throw new ClientException("Invalid URL passed for $arg: '$url'");
}
}
@@ -199,4 +207,3 @@ class PushHubAction extends Action
return HubSub::staticGet($feed, $callback);
}
}
-
diff --git a/plugins/OStatus/actions/userxrd.php b/plugins/OStatus/actions/userxrd.php
index 6a6886eb8..dd720568b 100644
--- a/plugins/OStatus/actions/userxrd.php
+++ b/plugins/OStatus/actions/userxrd.php
@@ -33,7 +33,7 @@ class UserxrdAction extends XrdAction
$this->uri = $this->trimmed('uri');
$this->uri = Discovery::normalize($this->uri);
-
+
if (Discovery::isWebfinger($this->uri)) {
$parts = explode('@', substr(urldecode($this->uri), 5));
if (count($parts) == 2) {
diff --git a/plugins/OStatus/classes/FeedSub.php b/plugins/OStatus/classes/FeedSub.php
index dd1968db1..6f9e0856a 100644
--- a/plugins/OStatus/classes/FeedSub.php
+++ b/plugins/OStatus/classes/FeedSub.php
@@ -249,7 +249,7 @@ class FeedSub extends Memcached_DataObject
// We'll never actually get updates in this mode.
return true;
} else {
- throw new ServerException("Attempting to start PuSH subscription for feed with no hub");
+ throw new ServerException("Attempting to start PuSH subscription for feed with no hub.");
}
}
@@ -279,7 +279,7 @@ class FeedSub extends Memcached_DataObject
// We'll never actually get updates in this mode.
return true;
} else {
- throw new ServerException("Attempting to end PuSH subscription for feed with no hub");
+ throw new ServerException("Attempting to end PuSH subscription for feed with no hub.");
}
}
@@ -502,4 +502,3 @@ class FeedSub extends Memcached_DataObject
}
}
-
diff --git a/plugins/OStatus/classes/HubSub.php b/plugins/OStatus/classes/HubSub.php
index 7db528a4e..e01ae4e79 100644
--- a/plugins/OStatus/classes/HubSub.php
+++ b/plugins/OStatus/classes/HubSub.php
@@ -206,6 +206,7 @@ class HubSub extends Memcached_DataObject
if ($status >= 200 && $status < 300) {
common_log(LOG_INFO, "Verified $mode of $this->callback:$this->topic");
} else {
+ // @todo i18n FIXME: add i18n and use sprintf for parameter.
throw new ClientException("Hub subscriber verification returned HTTP $status");
}
@@ -307,9 +308,9 @@ class HubSub extends Memcached_DataObject
/**
* Queue up a large batch of pushes to multiple subscribers
* for this same topic update.
- *
+ *
* If queues are disabled, this will run immediately.
- *
+ *
* @param string $atom well-formed Atom feed
* @param array $pushCallbacks list of callback URLs
*/
@@ -359,4 +360,3 @@ class HubSub extends Memcached_DataObject
}
}
}
-
diff --git a/plugins/OStatus/classes/Magicsig.php b/plugins/OStatus/classes/Magicsig.php
index f8c56a05f..e39a6d8f7 100644
--- a/plugins/OStatus/classes/Magicsig.php
+++ b/plugins/OStatus/classes/Magicsig.php
@@ -33,21 +33,21 @@ class Magicsig extends Memcached_DataObject
{
const PUBLICKEYREL = 'magic-public-key';
-
+
public $__table = 'magicsig';
public $user_id;
public $keypair;
public $alg;
-
+
public $publicKey;
public $privateKey;
-
+
public function __construct($alg = 'RSA-SHA256')
{
$this->alg = $alg;
}
-
+
public /*static*/ function staticGet($k, $v=null)
{
$obj = parent::staticGet(__CLASS__, $k, $v);
@@ -111,7 +111,7 @@ class Magicsig extends Memcached_DataObject
public function generate($user_id)
{
$rsa = new Crypt_RSA();
-
+
$keypair = $rsa->createKey();
$rsa->loadKey($keypair['privatekey']);
@@ -121,7 +121,7 @@ class Magicsig extends Memcached_DataObject
$this->publicKey = new Crypt_RSA();
$this->publicKey->loadKey($keypair['publickey']);
-
+
$this->user_id = $user_id;
$this->insert();
}
@@ -136,13 +136,13 @@ class Magicsig extends Memcached_DataObject
$private_exp = '.' . Magicsig::base64_url_encode($this->privateKey->exponent->toBytes());
}
- return 'RSA.' . $mod . '.' . $exp . $private_exp;
+ return 'RSA.' . $mod . '.' . $exp . $private_exp;
}
-
+
public static function fromString($text)
{
$magic_sig = new Magicsig();
-
+
// remove whitespace
$text = preg_replace('/\s+/', '', $text);
@@ -150,7 +150,7 @@ class Magicsig extends Memcached_DataObject
if (!preg_match('/RSA\.([^\.]+)\.([^\.]+)(.([^\.]+))?/', $text, $matches)) {
return false;
}
-
+
$mod = $matches[1];
$exp = $matches[2];
if (!empty($matches[4])) {
@@ -184,7 +184,7 @@ class Magicsig extends Memcached_DataObject
$this->publicKey = $rsa;
}
}
-
+
public function getName()
{
return $this->alg;
@@ -199,7 +199,7 @@ class Magicsig extends Memcached_DataObject
}
}
-
+
public function sign($bytes)
{
$sig = $this->privateKey->sign($bytes);
@@ -223,5 +223,3 @@ class Magicsig extends Memcached_DataObject
return base64_decode(strtr($input, '-_', '+/'));
}
}
-
-
diff --git a/plugins/OStatus/classes/Ostatus_profile.php b/plugins/OStatus/classes/Ostatus_profile.php
index 898c63e83..19fe5169b 100644
--- a/plugins/OStatus/classes/Ostatus_profile.php
+++ b/plugins/OStatus/classes/Ostatus_profile.php
@@ -188,9 +188,11 @@ class Ostatus_profile extends Memcached_DataObject
} else if ($this->group_id && !$this->profile_id) {
return true;
} else if ($this->group_id && $this->profile_id) {
- throw new ServerException("Invalid ostatus_profile state: both group and profile IDs set for $this->uri");
+ // @todo i18n FIXME: use sprintf and add i18n.
+ throw new ServerException("Invalid ostatus_profile state: both group and profile IDs set for $this->uri.");
} else {
- throw new ServerException("Invalid ostatus_profile state: both group and profile IDs empty for $this->uri");
+ // @todo i18n FIXME: use sprintf and add i18n.
+ throw new ServerException("Invalid ostatus_profile state: both group and profile IDs empty for $this->uri.");
}
}
@@ -370,7 +372,8 @@ class Ostatus_profile extends Memcached_DataObject
} else if ($entry instanceof Notice) {
return $preamble . $entry->asAtomEntry(true, true);
} else {
- throw new ServerException("Invalid type passed to Ostatus_profile::notify; must be XML string or Activity entry");
+ // @todo i18n FIXME: use sprintf and add i18n.
+ throw new ServerException("Invalid type passed to Ostatus_profile::notify; must be XML string or Activity entry.");
}
}
@@ -549,7 +552,8 @@ class Ostatus_profile extends Memcached_DataObject
$sourceContent = $note->title;
} else {
// @fixme fetch from $sourceUrl?
- throw new ClientException("No content for notice {$sourceUri}");
+ // @todo i18n FIXME: use sprintf and add i18n.
+ throw new ClientException("No content for notice {$sourceUri}.");
}
// Get (safe!) HTML and text versions of the content
@@ -587,7 +591,7 @@ class Ostatus_profile extends Memcached_DataObject
' class="attachment more"' .
' title="'. htmlspecialchars(_m('Show more')) . '">' .
'&#8230;' .
- '</a>';
+ '</a>'; // @todo i18n FIXME: add translator hint/context.
}
}
@@ -772,6 +776,7 @@ class Ostatus_profile extends Memcached_DataObject
$response = $client->get($profile_url);
if (!$response->isOk()) {
+ // @todo i18n FIXME: use sprintf and add i18n.
throw new Exception("Could not reach profile page: " . $profile_url);
}
@@ -829,6 +834,7 @@ class Ostatus_profile extends Memcached_DataObject
return self::ensureFeedURL($feedurl, $hints);
}
+ // @todo i18n FIXME: use sprintf and add i18n.
throw new Exception("Could not find a feed URL for profile page " . $finalUrl);
}
@@ -861,6 +867,7 @@ class Ostatus_profile extends Memcached_DataObject
$user = User::staticGet('id', $profile->id);
if (!empty($user)) {
+ // @todo i18n FIXME: use sprintf and add i18n.
throw new OStatusShadowException($profile, "'$profile_url' is the profile for local user '{$user->nickname}'.");
}
@@ -1025,7 +1032,7 @@ class Ostatus_profile extends Memcached_DataObject
return;
}
if (!common_valid_http_url($url)) {
- throw new ServerException(sprintf(_m("Invalid avatar URL %s"), $url));
+ throw new ServerException(sprintf(_m("Invalid avatar URL %s."), $url));
}
if ($this->isGroup()) {
@@ -1035,7 +1042,7 @@ class Ostatus_profile extends Memcached_DataObject
}
if (!$self) {
throw new ServerException(sprintf(
- _m("Tried to update avatar for unsaved remote profile %s"),
+ _m("Tried to update avatar for unsaved remote profile %s."),
$this->uri));
}
@@ -1043,7 +1050,7 @@ class Ostatus_profile extends Memcached_DataObject
// ripped from oauthstore.php (for old OMB client)
$temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
if (!copy($url, $temp_filename)) {
- throw new ServerException(sprintf(_m("Unable to fetch avatar from %s"), $url));
+ throw new ServerException(sprintf(_m("Unable to fetch avatar from %s."), $url));
}
if ($this->isGroup()) {
@@ -1058,6 +1065,14 @@ class Ostatus_profile extends Memcached_DataObject
null,
common_timestamp());
rename($temp_filename, Avatar::path($filename));
+ // @fixme hardcoded chmod is lame, but seems to be necessary to
+ // keep from accidentally saving images from command-line (queues)
+ // that can't be read from web server, which causes hard-to-notice
+ // problems later on:
+ //
+ // http://status.net/open-source/issues/2663
+ chmod(Avatar::path($filename), 0644);
+
$self->setOriginal($filename);
$orig = clone($this);
@@ -1226,7 +1241,7 @@ class Ostatus_profile extends Memcached_DataObject
if ($object->link && common_valid_http_url($object->link)) {
return $object->link;
}
- throw new ServerException("No author ID URI found");
+ throw new ServerException("No author ID URI found.");
}
/**
@@ -1256,10 +1271,12 @@ class Ostatus_profile extends Memcached_DataObject
$user = User::staticGet('uri', $homeuri);
if ($user) {
+ // @todo i18n FIXME: add i18n.
throw new Exception("Local user can't be referenced as remote.");
}
if (OStatusPlugin::localGroupFromUrl($homeuri)) {
+ // @todo i18n FIXME: add i18n.
throw new Exception("Local group can't be referenced as remote.");
}
@@ -1311,7 +1328,8 @@ class Ostatus_profile extends Memcached_DataObject
$oprofile->profile_id = $profile->insert();
if (!$oprofile->profile_id) {
- throw new ServerException("Can't save local profile");
+ // @todo i18n FIXME: add i18n.
+ throw new ServerException("Can't save local profile.");
}
} else {
$group = new User_group();
@@ -1321,14 +1339,16 @@ class Ostatus_profile extends Memcached_DataObject
$oprofile->group_id = $group->insert();
if (!$oprofile->group_id) {
- throw new ServerException("Can't save local profile");
+ // @todo i18n FIXME: add i18n.
+ throw new ServerException("Can't save local profile.");
}
}
$ok = $oprofile->insert();
if (!$ok) {
- throw new ServerException("Can't save OStatus profile");
+ // @todo i18n FIXME: add i18n.
+ throw new ServerException("Can't save OStatus profile.");
}
$avatar = self::getActivityObjectAvatar($object, $hints);
@@ -1586,6 +1606,7 @@ class Ostatus_profile extends Memcached_DataObject
if ($uri !== false) {
if (is_null($uri)) {
// Negative cache entry
+ // @todo i18n FIXME: add i18n.
throw new Exception('Not a valid webfinger address.');
}
$oprofile = Ostatus_profile::staticGet('uri', $uri);
@@ -1613,6 +1634,7 @@ class Ostatus_profile extends Memcached_DataObject
// Save negative cache entry so we don't waste time looking it up again.
// @fixme distinguish temporary failures?
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), null);
+ // @todo i18n FIXME: add i18n.
throw new Exception('Not a valid webfinger address.');
}
@@ -1694,7 +1716,8 @@ class Ostatus_profile extends Memcached_DataObject
if (!$profile_id) {
common_log_db_error($profile, 'INSERT', __FILE__);
- throw new Exception("Couldn't save profile for '$addr'");
+ // @todo i18n FIXME: add i18n and use sprintf for parameter.
+ throw new Exception("Couldn't save profile for '$addr'.");
}
$oprofile = new Ostatus_profile();
@@ -1712,13 +1735,15 @@ class Ostatus_profile extends Memcached_DataObject
if (!$result) {
common_log_db_error($oprofile, 'INSERT', __FILE__);
- throw new Exception("Couldn't save ostatus_profile for '$addr'");
+ // @todo i18n FIXME: add i18n and use sprintf for parameter.
+ throw new Exception("Couldn't save ostatus_profile for '$addr'.");
}
self::cacheSet(sprintf('ostatus_profile:webfinger:%s', $addr), $oprofile->uri);
return $oprofile;
}
+ // @todo i18n FIXME: add i18n and use sprintf for parameter.
throw new Exception("Couldn't find a valid profile for '$addr'");
}
@@ -1818,4 +1843,3 @@ class OStatusShadowException extends Exception
parent::__construct($message);
}
}
-
diff --git a/plugins/OStatus/lib/discovery.php b/plugins/OStatus/lib/discovery.php
index 7187c1f3e..04c672720 100644
--- a/plugins/OStatus/lib/discovery.php
+++ b/plugins/OStatus/lib/discovery.php
@@ -106,7 +106,8 @@ class Discovery
}
}
- throw new Exception('Unable to find services for '. $id);
+ // @todo Needs i18n.
+ throw new Exception('Unable to find services for '. $id . '.');
}
public static function getService($links, $service) {
@@ -160,7 +161,7 @@ class Discovery_LRDD_Host_Meta implements Discovery_LRDD
} else {
$domain = parse_url($uri, PHP_URL_HOST);
}
-
+
$url = 'http://'. $domain .'/.well-known/host-meta';
$xrd = Discovery::fetchXrd($url);
diff --git a/plugins/OStatus/lib/hubconfqueuehandler.php b/plugins/OStatus/lib/hubconfqueuehandler.php
index c8e0b72fe..8219f8420 100644
--- a/plugins/OStatus/lib/hubconfqueuehandler.php
+++ b/plugins/OStatus/lib/hubconfqueuehandler.php
@@ -51,4 +51,3 @@ class HubConfQueueHandler extends QueueHandler
return true;
}
}
-
diff --git a/plugins/OStatus/lib/magicenvelope.php b/plugins/OStatus/lib/magicenvelope.php
index 967e5f6d1..bbd4ce17a 100644
--- a/plugins/OStatus/lib/magicenvelope.php
+++ b/plugins/OStatus/lib/magicenvelope.php
@@ -32,7 +32,7 @@ class MagicEnvelope
const ENCODING = 'base64url';
const NS = 'http://salmon-protocol.org/ns/magic-env';
-
+
private function normalizeUser($user_id)
{
if (substr($user_id, 0, 5) == 'http:' ||
@@ -70,13 +70,13 @@ class MagicEnvelope
$keypair = $parts[1];
}
}
-
+
if ($keypair) {
return $keypair;
}
}
}
- throw new Exception('Unable to locate signer public key');
+ throw new Exception('Unable to locate signer public key.');
}
@@ -92,8 +92,7 @@ class MagicEnvelope
'sig' => $signature_alg->sign($armored_text),
'alg' => $signature_alg->getName()
);
-
-
+
}
public function toXML($env) {
@@ -105,13 +104,13 @@ class MagicEnvelope
$xs->element('me:alg', null, $env['alg']);
$xs->element('me:sig', null, $env['sig']);
$xs->elementEnd('me:env');
-
+
$string = $xs->getString();
common_debug($string);
return $string;
}
-
+
public function unfold($env)
{
$dom = new DOMDocument();
@@ -137,7 +136,7 @@ class MagicEnvelope
return $dom->saveXML();
}
-
+
public function getAuthor($text) {
$doc = new DOMDocument();
if (!$doc->loadXML($text)) {
@@ -154,12 +153,12 @@ class MagicEnvelope
}
}
}
-
+
public function checkAuthor($text, $signer_uri)
{
return ($this->getAuthor($text) == $signer_uri);
}
-
+
public function verify($env)
{
if ($env['alg'] != 'RSA-SHA256') {
@@ -181,14 +180,14 @@ class MagicEnvelope
common_log(LOG_DEBUG, "Salmon error: ".$e->getMessage());
return false;
}
-
+
$verifier = Magicsig::fromString($keypair);
if (!$verifier) {
common_log(LOG_DEBUG, "Salmon error: unable to parse keypair");
return false;
}
-
+
return $verifier->verify($env['data'], $env['sig']);
}
diff --git a/plugins/OStatus/lib/ostatusqueuehandler.php b/plugins/OStatus/lib/ostatusqueuehandler.php
index 5e318116a..9814cab9f 100644
--- a/plugins/OStatus/lib/ostatusqueuehandler.php
+++ b/plugins/OStatus/lib/ostatusqueuehandler.php
@@ -223,4 +223,3 @@ class OStatusQueueHandler extends QueueHandler
}
}
-
diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php
index ef7719a40..631ebc7d8 100644
--- a/plugins/OStatus/lib/salmon.php
+++ b/plugins/OStatus/lib/salmon.php
@@ -31,10 +31,10 @@ class Salmon
const REL_SALMON = 'salmon';
const REL_MENTIONED = 'mentioned';
- // XXX: these are deprecated
+ // XXX: these are deprecated
const NS_REPLIES = "http://salmon-protocol.org/ns/salmon-replies";
const NS_MENTIONS = "http://salmon-protocol.org/ns/salmon-mention";
-
+
/**
* Sign and post the given Atom entry as a Salmon message.
*
@@ -87,9 +87,10 @@ class Salmon
// No keypair yet, let's generate one.
$magickey = new Magicsig();
$magickey->generate($user->id);
- }
+ }
} else {
- throw new Exception("Salmon invalid actor for signing");
+ // @todo i18n FIXME: added i18n and use sprintf when using parameters.
+ throw new Exception("Salmon invalid actor for signing.");
}
try {
@@ -104,7 +105,7 @@ class Salmon
public function verifyMagicEnv($text)
{
$magic_env = new MagicEnvelope();
-
+
$env = $magic_env->parse($text);
return $magic_env->verify($env);
diff --git a/plugins/OStatus/lib/salmonaction.php b/plugins/OStatus/lib/salmonaction.php
index 9d6c6b269..5fdb11abe 100644
--- a/plugins/OStatus/lib/salmonaction.php
+++ b/plugins/OStatus/lib/salmonaction.php
@@ -42,7 +42,7 @@ class SalmonAction extends Action
}
if (empty($_SERVER['CONTENT_TYPE']) || $_SERVER['CONTENT_TYPE'] != 'application/magic-envelope+xml') {
- $this->clientError(_m('Salmon requires application/magic-envelope+xml'));
+ $this->clientError(_m('Salmon requires "application/magic-envelope+xml".'));
}
$xml = file_get_contents('php://input');
diff --git a/plugins/OStatus/lib/salmonqueuehandler.php b/plugins/OStatus/lib/salmonqueuehandler.php
index 7eeb5f8e9..56d3c9eff 100644
--- a/plugins/OStatus/lib/salmonqueuehandler.php
+++ b/plugins/OStatus/lib/salmonqueuehandler.php
@@ -36,7 +36,7 @@ class SalmonQueueHandler extends QueueHandler
assert(is_string($data['entry']));
$actor = Profile::staticGet($data['actor']);
-
+
$salmon = new Salmon();
$salmon->post($data['salmonuri'], $data['entry'], $actor);
diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php
index a10b9f427..145cd64cb 100644
--- a/plugins/OStatus/lib/xrd.php
+++ b/plugins/OStatus/lib/xrd.php
@@ -31,11 +31,11 @@
class XRD
{
const XML_NS = 'http://www.w3.org/2000/xmlns/';
-
+
const XRD_NS = 'http://docs.oasis-open.org/ns/xri/xrd-1.0';
const HOST_META_NS = 'http://host-meta.net/xrd/1.0';
-
+
public $expires;
public $subject;
@@ -43,11 +43,11 @@ class XRD
public $host;
public $alias = array();
-
+
public $types = array();
-
+
public $links = array();
-
+
public static function parse($xml)
{
$xrd = new XRD();
@@ -61,11 +61,11 @@ class XRD
error_reporting($old);
if (!$ok) {
- throw new Exception("Invalid XML");
+ throw new Exception("Invalid XML.");
}
$xrd_element = $dom->getElementsByTagName('XRD')->item(0);
if (!$xrd_element) {
- throw new Exception("Invalid XML, missing XRD root");
+ throw new Exception("Invalid XML, missing XRD root.");
}
// Check for host-meta host
@@ -86,7 +86,7 @@ class XRD
case 'Subject':
$xrd->subject = $node->nodeValue;
break;
-
+
case 'Alias':
$xrd->alias[] = $node->nodeValue;
break;
@@ -114,7 +114,7 @@ class XRD
if ($this->host) {
$xs->element('hm:Host', array('xmlns:hm' => XRD::HOST_META_NS), $this->host);
}
-
+
if ($this->expires) {
$xs->element('Expires', null, $this->expires);
}
@@ -139,7 +139,7 @@ class XRD
}
$xs->elementEnd('Link');
}
-
+
$xs->elementEnd('XRD');
return $xs->getString();
@@ -149,7 +149,7 @@ class XRD
{
return array();
}
-
+
function parseLink($element)
{
$link = array();
@@ -169,4 +169,3 @@ class XRD
return $link;
}
}
-
diff --git a/plugins/OStatus/lib/xrdaction.php b/plugins/OStatus/lib/xrdaction.php
index d8cf648d6..91bb87cc2 100644
--- a/plugins/OStatus/lib/xrdaction.php
+++ b/plugins/OStatus/lib/xrdaction.php
@@ -26,13 +26,12 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class XrdAction extends Action
{
-
public $uri;
-
+
public $user;
public $xrd;
-
+
function handle()
{
$nick = $this->user->nickname;
diff --git a/plugins/OStatus/scripts/fixup-shadow.php b/plugins/OStatus/scripts/fixup-shadow.php
index 6522ca240..4b6ad08a3 100644
--- a/plugins/OStatus/scripts/fixup-shadow.php
+++ b/plugins/OStatus/scripts/fixup-shadow.php
@@ -77,7 +77,7 @@ while ($oprofile->fetch()) {
echo "$uri matched query, but we don't recognize it.\n";
continue;
}
-
+
if ($dry) {
echo " - skipping\n";
} else {
@@ -93,4 +93,3 @@ if ($count && $dry) {
} else {
echo "done.\n";
}
-
diff --git a/plugins/OStatus/scripts/testfeed.php b/plugins/OStatus/scripts/testfeed.php
index 5e3ccd433..82a1c6586 100644
--- a/plugins/OStatus/scripts/testfeed.php
+++ b/plugins/OStatus/scripts/testfeed.php
@@ -86,4 +86,3 @@ if ($skip || $count) {
}
Event::handle('StartFeedSubReceive', array($sub, $feed));
-
diff --git a/plugins/OStatus/scripts/updateostatus.php b/plugins/OStatus/scripts/updateostatus.php
index ff0d86d37..052cca146 100644
--- a/plugins/OStatus/scripts/updateostatus.php
+++ b/plugins/OStatus/scripts/updateostatus.php
@@ -49,7 +49,7 @@ try {
$nickname = get_option_value('n', 'nickname');
$user = User::staticGet('nickname', $nickname);
if (empty($user)) {
- throw new Exception("Can't find user with nickname '$nickname'");
+ throw new Exception("Can't find user with nickname '$nickname'.");
}
updateOStatus($user);
} else if (have_option('a', 'all')) {
diff --git a/plugins/OStatus/tests/FeedDiscoveryTest.php b/plugins/OStatus/tests/FeedDiscoveryTest.php
index 0e6354a86..3be4bf736 100644
--- a/plugins/OStatus/tests/FeedDiscoveryTest.php
+++ b/plugins/OStatus/tests/FeedDiscoveryTest.php
@@ -53,7 +53,7 @@ class FeedDiscoveryTest extends PHPUnit_Framework_TestCase
</style>
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://leuksman.com/log/xmlrpc.php?rsd" />
-<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://leuksman.com/log/wp-includes/wlwmanifest.xml" />
+<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://leuksman.com/log/wp-includes/wlwmanifest.xml" />
<link rel='index' title='leÅ­ksman' href='http://leuksman.com/log' />
<meta name="generator" content="WordPress 2.8.6" />
</head>
diff --git a/plugins/OStatus/tests/remote-tests.php b/plugins/OStatus/tests/remote-tests.php
index 24b4b1660..c2c9a5d97 100644
--- a/plugins/OStatus/tests/remote-tests.php
+++ b/plugins/OStatus/tests/remote-tests.php
@@ -500,7 +500,7 @@ class SNTestClient extends TestBase
$me = $this->getProfileUri();
return $this->checkSubscription($profile_uri, $me);
}
-
+
protected function checkSubscription($subscriber, $subscribed)
{
// Using FOAF as the API methods for checking the social graph
@@ -552,4 +552,3 @@ $b = $args[1];
$tester = new OStatusTester($a, $b);
$tester->run();
-
diff --git a/plugins/TwitterBridge/Notice_to_status.php b/plugins/TwitterBridge/Notice_to_status.php
new file mode 100644
index 000000000..2e32ba963
--- /dev/null
+++ b/plugins/TwitterBridge/Notice_to_status.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * Data class for remembering notice-to-status mappings
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+/**
+ * Data class for mapping notices to statuses
+ *
+ * Notices flow back and forth between Twitter and StatusNet. We use this
+ * table to remember which StatusNet notice corresponds to which Twitter
+ * status.
+ *
+ * Note that notice_id is unique only within a single database; if you
+ * want to share this data for some reason, get the notice's URI and use
+ * that instead, since it's universally unique.
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * @see DB_DataObject
+ */
+
+class Notice_to_status extends Memcached_DataObject
+{
+ public $__table = 'notice_to_status'; // table name
+ public $notice_id; // int(4) primary_key not_null
+ public $status_id; // int(4)
+ public $created; // datetime
+
+ /**
+ * Get an instance by key
+ *
+ * This is a utility method to get a single instance with a given key value.
+ *
+ * @param string $k Key to use to lookup
+ * @param mixed $v Value to lookup
+ *
+ * @return Notice_to_status object found, or null for no hits
+ *
+ */
+
+ function staticGet($k, $v=null)
+ {
+ return Memcached_DataObject::staticGet('Notice_to_status', $k, $v);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
+ return array('notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'status_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * DB_DataObject needs to know about keys that the table has, since it
+ * won't appear in StatusNet's own keys list. In most cases, this will
+ * simply reference your keyTypes() function.
+ *
+ * @return array list of key field names
+ */
+
+ function keys()
+ {
+ return array_keys($this->keyTypes());
+ }
+
+ /**
+ * return key definitions for Memcached_DataObject
+ *
+ * Our caching system uses the same key definitions, but uses a different
+ * method to get them. This key information is used to store and clear
+ * cached data, so be sure to list any key that will be used for static
+ * lookups.
+ *
+ * @return array associative array of key definitions, field name to type:
+ * 'K' for primary key: for compound keys, add an entry for each component;
+ * 'U' for unique keys: compound keys are not well supported here.
+ */
+
+ function keyTypes()
+ {
+ return array('notice_id' => 'K', 'status_id' => 'U');
+ }
+
+ /**
+ * Magic formula for non-autoincrementing integer primary keys
+ *
+ * If a table has a single integer column as its primary key, DB_DataObject
+ * assumes that the column is auto-incrementing and makes a sequence table
+ * to do this incrementation. Since we don't need this for our class, we
+ * overload this method and return the magic formula that DB_DataObject needs.
+ *
+ * @return array magic three-false array that stops auto-incrementing.
+ */
+
+ function sequenceKey()
+ {
+ return array(false, false, false);
+ }
+
+ /**
+ * Save a mapping between a notice and a status
+ *
+ * @param integer $notice_id ID of the notice in StatusNet
+ * @param integer $status_id ID of the status in Twitter
+ *
+ * @return Notice_to_status new object for this value
+ */
+
+ static function saveNew($notice_id, $status_id)
+ {
+ $n2s = Notice_to_status::staticGet('notice_id', $notice_id);
+
+ if (!empty($n2s)) {
+ return $n2s;
+ }
+
+ $n2s = Notice_to_status::staticGet('status_id', $status_id);
+
+ if (!empty($n2s)) {
+ return $n2s;
+ }
+
+ common_debug("Mapping notice {$notice_id} to Twitter status {$status_id}");
+
+ $n2s = new Notice_to_status();
+
+ $n2s->notice_id = $notice_id;
+ $n2s->status_id = $status_id;
+ $n2s->created = common_sql_now();
+
+ $n2s->insert();
+
+ return $n2s;
+ }
+}
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
index 8e3eba318..21a10775d 100644
--- a/plugins/TwitterBridge/TwitterBridgePlugin.php
+++ b/plugins/TwitterBridge/TwitterBridgePlugin.php
@@ -194,18 +194,22 @@ class TwitterBridgePlugin extends Plugin
*/
function onAutoload($cls)
{
+ $dir = dirname(__FILE__);
+
switch ($cls) {
case 'TwittersettingsAction':
case 'TwitterauthorizationAction':
case 'TwitterloginAction':
case 'TwitteradminpanelAction':
- include_once INSTALLDIR . '/plugins/TwitterBridge/' .
- strtolower(mb_substr($cls, 0, -6)) . '.php';
+ include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
case 'TwitterOAuthClient':
case 'TwitterQueueHandler':
- include_once INSTALLDIR . '/plugins/TwitterBridge/' .
- strtolower($cls) . '.php';
+ include_once $dir . '/' . strtolower($cls) . '.php';
+ return false;
+ case 'Notice_to_status':
+ case 'Twitter_synch_status':
+ include_once $dir . '/' . $cls . '.php';
return false;
default:
return true;
@@ -360,5 +364,183 @@ class TwitterBridgePlugin extends Plugin
}
}
-}
+ /**
+ * Database schema setup
+ *
+ * We maintain a table mapping StatusNet notices to Twitter statuses
+ *
+ * @see Schema
+ * @see ColumnDef
+ *
+ * @return boolean hook value; true means continue processing, false means stop.
+ */
+
+ function onCheckSchema()
+ {
+ $schema = Schema::get();
+
+ // For saving the last-synched status of various timelines
+ // home_timeline, messages (in), messages (out), ...
+
+ $schema->ensureTable('twitter_synch_status',
+ array(new ColumnDef('foreign_id', 'bigint', null,
+ false, 'PRI'),
+ new ColumnDef('timeline', 'varchar', 255,
+ false, 'PRI'),
+ new ColumnDef('last_id', 'bigint', null, // XXX: check for PostgreSQL
+ false),
+ new ColumnDef('created', 'datetime', null,
+ false),
+ new ColumnDef('modified', 'datetime', null,
+ false)));
+
+ // For storing user-submitted flags on profiles
+
+ $schema->ensureTable('notice_to_status',
+ array(new ColumnDef('notice_id', 'integer', null,
+ false, 'PRI'),
+ new ColumnDef('status_id', 'bigint', null, // XXX: check for PostgreSQL
+ false, 'UNI'),
+ new ColumnDef('created', 'datetime', null,
+ false)));
+
+ // We update any notices that may have come in from
+ // Twitter that we don't have a status_id for. Note that
+ // this won't catch notices that originated at this StatusNet site.
+
+ $n = new Notice();
+
+ $n->query('SELECT notice.id, notice.uri ' .
+ 'FROM notice LEFT JOIN notice_to_status ' .
+ 'ON notice.id = notice_to_status.notice_id ' .
+ 'WHERE notice.source = "twitter"' .
+ 'AND notice_to_status.status_id IS NULL');
+
+ while ($n->fetch()) {
+ if (preg_match('#^http://twitter.com/[\w_.]+/status/(\d+)$#', $n->uri, $match)) {
+
+ $status_id = $match[1];
+
+ Notice_to_status::saveNew($n->id, $status_id);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * If a notice gets deleted, remove the Notice_to_status mapping
+ *
+ * @param Notice $notice The notice getting deleted
+ *
+ * @return boolean hook value
+ */
+ function onNoticeDeleteRelated($notice)
+ {
+ $n2s = Notice_to_status::staticGet('notice_id', $notice->id);
+
+ if (!empty($n2s)) {
+
+ $user = common_current_user();
+
+ if (empty($user) || $user->id != $notice->profile_id) {
+ $this->log(LOG_INFO, "Skipping deleting notice for {$notice->id} since it doesn't seem to be by the author.");
+ return true;
+ }
+
+ $flink = Foreign_link::getByUserID($notice->profile_id,
+ TWITTER_SERVICE); // twitter service
+
+ if (empty($flink)) {
+ return true;
+ }
+
+ if (!TwitterOAuthClient::isPackedToken($flink->credentials)) {
+ $this->log(LOG_INFO, "Skipping deleting notice for {$notice->id} since link is not OAuth.");
+ return true;
+ }
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ $client->statusesDestroy($n2s->status_id);
+
+ $n2s->delete();
+ }
+ return true;
+ }
+
+ /**
+ * Notify remote users when their notices get favorited.
+ *
+ * @param Profile or User $profile of local user doing the faving
+ * @param Notice $notice being favored
+ * @return hook return value
+ */
+
+ function onEndFavorNotice(Profile $profile, Notice $notice)
+ {
+ $flink = Foreign_link::getByUserID($profile->id,
+ TWITTER_SERVICE); // twitter service
+
+ if (empty($flink)) {
+ return true;
+ }
+
+ if (!TwitterOAuthClient::isPackedToken($flink->credentials)) {
+ $this->log(LOG_INFO, "Skipping fave processing for {$profile->id} since link is not OAuth.");
+ return true;
+ }
+
+ $status_id = twitter_status_id($notice);
+
+ if (empty($status_id)) {
+ return true;
+ }
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ $client->favoritesCreate($status_id);
+
+ return true;
+ }
+
+ /**
+ * Notify remote users when their notices get de-favorited.
+ *
+ * @param Profile $profile Profile person doing the de-faving
+ * @param Notice $notice Notice being favored
+ *
+ * @return hook return value
+ */
+
+ function onEndDisfavorNotice(Profile $profile, Notice $notice)
+ {
+ $flink = Foreign_link::getByUserID($profile->id,
+ TWITTER_SERVICE); // twitter service
+
+ if (empty($flink)) {
+ return true;
+ }
+
+ if (!TwitterOAuthClient::isPackedToken($flink->credentials)) {
+ $this->log(LOG_INFO, "Skipping fave processing for {$profile->id} since link is not OAuth.");
+ return true;
+ }
+
+ $status_id = twitter_status_id($notice);
+
+ if (empty($status_id)) {
+ return true;
+ }
+
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ $client->favoritesDestroy($status_id);
+
+ return true;
+ }
+}
diff --git a/plugins/TwitterBridge/Twitter_synch_status.php b/plugins/TwitterBridge/Twitter_synch_status.php
new file mode 100644
index 000000000..2a5f1fd60
--- /dev/null
+++ b/plugins/TwitterBridge/Twitter_synch_status.php
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Store last-touched ID for various timelines
+ *
+ * PHP version 5
+ *
+ * @category Data
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2010, StatusNet, Inc.
+ *
+ * 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/>.
+ */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+/**
+ * Store various timeline data
+ *
+ * We don't want to keep re-fetching the same statuses and direct messages from Twitter.
+ * So, we store the last ID we see from a timeline, and store it. Next time
+ * around, we use that ID in the since_id parameter.
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
+ * @link http://status.net/
+ *
+ * @see DB_DataObject
+ */
+
+class Twitter_synch_status extends Memcached_DataObject
+{
+ public $__table = 'twitter_synch_status'; // table name
+ public $foreign_id; // int(4) primary_key not_null
+ public $timeline; // varchar(255) primary_key not_null
+ public $last_id; // bigint not_null
+ public $created; // datetime not_null
+ public $modified; // datetime not_null
+
+ /**
+ * Get an instance by key
+ *
+ * @param string $k Key to use to lookup (usually 'foreign_id' for this class)
+ * @param mixed $v Value to lookup
+ *
+ * @return Twitter_synch_status object found, or null for no hits
+ *
+ */
+
+ function staticGet($k, $v=null)
+ {
+ throw new Exception("Use pkeyGet() for this class.");
+ }
+
+ /**
+ * Get an instance by compound primary key
+ *
+ * @param array $kv key-value pair array
+ *
+ * @return Twitter_synch_status object found, or null for no hits
+ *
+ */
+
+ function pkeyGet($kv)
+ {
+ return Memcached_DataObject::pkeyGet('Twitter_synch_status', $kv);
+ }
+
+ /**
+ * return table definition for DB_DataObject
+ *
+ * DB_DataObject needs to know something about the table to manipulate
+ * instances. This method provides all the DB_DataObject needs to know.
+ *
+ * @return array array of column definitions
+ */
+
+ function table()
+ {
+ return array('foreign_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'timeline' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+ 'last_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+ 'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
+ 'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL
+ );
+ }
+
+ /**
+ * return key definitions for DB_DataObject
+ *
+ * DB_DataObject needs to know about keys that the table has, since it
+ * won't appear in StatusNet's own keys list. In most cases, this will
+ * simply reference your keyTypes() function.
+ *
+ * @return array list of key field names
+ */
+
+ function keys()
+ {
+ return array_keys($this->keyTypes());
+ }
+
+ /**
+ * return key definitions for Memcached_DataObject
+ *
+ * Our caching system uses the same key definitions, but uses a different
+ * method to get them. This key information is used to store and clear
+ * cached data, so be sure to list any key that will be used for static
+ * lookups.
+ *
+ * @return array associative array of key definitions, field name to type:
+ * 'K' for primary key: for compound keys, add an entry for each component;
+ * 'U' for unique keys: compound keys are not well supported here.
+ */
+
+ function keyTypes()
+ {
+ return array('foreign_id' => 'K',
+ 'timeline' => 'K');
+ }
+
+ /**
+ * Magic formula for non-autoincrementing integer primary keys
+ *
+ * If a table has a single integer column as its primary key, DB_DataObject
+ * assumes that the column is auto-incrementing and makes a sequence table
+ * to do this incrementation. Since we don't need this for our class, we
+ * overload this method and return the magic formula that DB_DataObject needs.
+ *
+ * @return array magic three-false array that stops auto-incrementing.
+ */
+
+ function sequenceKey()
+ {
+ return array(false, false, false);
+ }
+
+ static function getLastId($foreign_id, $timeline)
+ {
+ $tss = self::pkeyGet(array('foreign_id' => $foreign_id,
+ 'timeline' => $timeline));
+
+ if (empty($tss)) {
+ return null;
+ } else {
+ return $tss->last_id;
+ }
+ }
+
+ static function setLastId($foreign_id, $timeline, $last_id)
+ {
+ $tss = self::pkeyGet(array('foreign_id' => $foreign_id,
+ 'timeline' => $timeline));
+
+ if (empty($tss)) {
+
+ $tss = new Twitter_synch_status();
+
+ $tss->foreign_id = $foreign_id;
+ $tss->timeline = $timeline;
+ $tss->last_id = $last_id;
+ $tss->created = common_sql_now();
+ $tss->modified = $tss->created;
+
+ $tss->insert();
+
+ return true;
+
+ } else {
+
+ $orig = clone($tss);
+
+ $tss->last_id = $last_id;
+ $tss->modified = common_sql_now();
+
+ $tss->update();
+
+ return true;
+ }
+ }
+}
diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php
index df7da0943..02546a02c 100755
--- a/plugins/TwitterBridge/daemons/synctwitterfriends.php
+++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php
@@ -33,7 +33,6 @@ END_OF_TRIM_HELP;
require_once INSTALLDIR . '/scripts/commandline.inc';
require_once INSTALLDIR . '/lib/parallelizingdaemon.php';
require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
-require_once INSTALLDIR . '/plugins/TwitterBridge/twitterbasicauthclient.php';
require_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php';
/**
@@ -144,8 +143,8 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon
$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.');
+ common_debug("Skipping Twitter friends for {$flink->user_id} since not OAuth.");
+ return $friends;
}
try {
diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
index 7c624fdb3..f1305696b 100755
--- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
+++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
@@ -40,7 +40,6 @@ require_once INSTALLDIR . '/scripts/commandline.inc';
require_once INSTALLDIR . '/lib/common.php';
require_once INSTALLDIR . '/lib/daemon.php';
require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
-require_once INSTALLDIR . '/plugins/TwitterBridge/twitterbasicauthclient.php';
require_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php';
/**
@@ -104,7 +103,6 @@ class TwitterStatusFetcher extends ParallelizingDaemon
function getObjects()
{
global $_DB_DATAOBJECT;
-
$flink = new Foreign_link();
$conn = &$flink->getDatabaseConnection();
@@ -168,10 +166,6 @@ class TwitterStatusFetcher extends ParallelizingDaemon
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.
-
$client = null;
if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
@@ -179,14 +173,17 @@ class TwitterStatusFetcher extends ParallelizingDaemon
$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.');
+ common_debug("Skipping friends timeline for $flink->foreign_id since not OAuth.");
}
$timeline = null;
+ $lastId = Twitter_synch_status::getLastId($flink->foreign_id, 'home_timeline');
+
+ common_debug("Got lastId value '{$lastId}' for foreign id '{$flink->foreign_id}' and timeline 'home_timeline'");
+
try {
- $timeline = $client->statusesFriendsTimeline();
+ $timeline = $client->statusesHomeTimeline($lastId);
} catch (Exception $e) {
common_log(LOG_WARNING, $this->name() .
' - Twitter client unable to get friends timeline for user ' .
@@ -215,7 +212,23 @@ class TwitterStatusFetcher extends ParallelizingDaemon
continue;
}
- $this->saveStatus($status, $flink);
+ // Don't save it if the user is protected
+ // FIXME: save it but treat it as private
+
+ if ($status->user->protected) {
+ continue;
+ }
+
+ $notice = $this->saveStatus($status);
+
+ if (!empty($notice)) {
+ Inbox::insertNotice($flink->user_id, $notice->id);
+ }
+ }
+
+ if (!empty($timeline)) {
+ Twitter_synch_status::setLastId($flink->foreign_id, 'home_timeline', $timeline[0]->id);
+ common_debug("Set lastId value '{$timeline[0]->id}' for foreign id '{$flink->foreign_id}' and timeline 'home_timeline'");
}
// Okay, record the time we synced with Twitter for posterity
@@ -224,32 +237,61 @@ class TwitterStatusFetcher extends ParallelizingDaemon
$flink->update();
}
- function saveStatus($status, $flink)
+ function saveStatus($status)
{
$profile = $this->ensureProfile($status->user);
if (empty($profile)) {
common_log(LOG_ERR, $this->name() .
' - Problem saving notice. No associated Profile.');
- return;
+ return null;
}
- $statusUri = 'http://twitter.com/'
- . $status->user->screen_name
- . '/status/'
- . $status->id;
+ $statusUri = $this->makeStatusURI($status->user->screen_name, $status->id);
// check to see if we've already imported the status
- $dupe = $this->checkDupe($profile, $statusUri);
+ $n2s = Notice_to_status::staticGet('status_id', $status->id);
- if (!empty($dupe)) {
+ if (!empty($n2s)) {
common_log(
LOG_INFO,
$this->name() .
- " - Ignoring duplicate import: $statusUri"
+ " - Ignoring duplicate import: {$status->id}"
);
- return;
+ return Notice::staticGet('id', $n2s->notice_id);
+ }
+
+ // If it's a retweet, save it as a repeat!
+
+ if (!empty($status->retweeted_status)) {
+ common_log(LOG_INFO, "Status {$status->id} is a retweet of {$status->retweeted_status->id}.");
+ $original = $this->saveStatus($status->retweeted_status);
+ if (empty($original)) {
+ return null;
+ } else {
+ $author = $original->getProfile();
+ // TRANS: Message used to repeat a notice. RT is the abbreviation of 'retweet'.
+ // TRANS: %1$s is the repeated user's name, %2$s is the repeated notice.
+ $content = sprintf(_('RT @%1$s %2$s'),
+ $author->nickname,
+ $original->content);
+
+ if (Notice::contentTooLong($content)) {
+ $contentlimit = Notice::maxContent();
+ $content = mb_substr($content, 0, $contentlimit - 4) . ' ...';
+ }
+
+ $repeat = Notice::saveNew($profile->id,
+ $content,
+ 'twitter',
+ array('repeat_of' => $original->id,
+ 'uri' => $statusUri,
+ 'is_local' => Notice::GATEWAY));
+ common_log(LOG_INFO, "Saved {$repeat->id} as a repeat of {$original->id}");
+ Notice_to_status::saveNew($repeat->id, $status->id);
+ return $repeat;
+ }
}
$notice = new Notice();
@@ -263,14 +305,36 @@ class TwitterStatusFetcher extends ParallelizingDaemon
);
$notice->source = 'twitter';
+
$notice->reply_to = null;
+
+ if (!empty($status->in_reply_to_status_id)) {
+ common_log(LOG_INFO, "Status {$status->id} is a reply to status {$status->in_reply_to_status_id}");
+ $n2s = Notice_to_status::staticGet('status_id', $status->in_reply_to_status_id);
+ if (empty($n2s)) {
+ common_log(LOG_INFO, "Couldn't find local notice for status {$status->in_reply_to_status_id}");
+ } else {
+ $reply = Notice::staticGet('id', $n2s->notice_id);
+ if (empty($reply)) {
+ common_log(LOG_INFO, "Couldn't find local notice for status {$status->in_reply_to_status_id}");
+ } else {
+ common_log(LOG_INFO, "Found local notice {$reply->id} for status {$status->in_reply_to_status_id}");
+ $notice->reply_to = $reply->id;
+ $notice->conversation = $reply->conversation;
+ }
+ }
+ }
+
+ if (empty($notice->conversation)) {
+ $conv = Conversation::create();
+ $notice->conversation = $conv->id;
+ common_log(LOG_INFO, "No known conversation for status {$status->id} so making a new one {$conv->id}.");
+ }
+
$notice->is_local = Notice::GATEWAY;
- $notice->content = common_shorten_links($status->text);
- $notice->rendered = common_render_content(
- $notice->content,
- $notice
- );
+ $notice->content = html_entity_decode($status->text);
+ $notice->rendered = $this->linkify($status);
if (Event::handle('StartNoticeSave', array(&$notice))) {
@@ -285,24 +349,32 @@ class TwitterStatusFetcher extends ParallelizingDaemon
Event::handle('EndNoticeSave', array($notice));
}
- $orig = clone($notice);
- $conv = Conversation::create();
+ Notice_to_status::saveNew($notice->id, $status->id);
- $notice->conversation = $conv->id;
+ $this->saveStatusMentions($notice, $status);
- if (!$notice->update($orig)) {
- common_log_db_error($notice, 'UPDATE', __FILE__);
- common_log(LOG_ERR, $this->name() .
- ' - Problem saving notice.');
- }
-
- Inbox::insertNotice($flink->user_id, $notice->id);
$notice->blowOnInsert();
return $notice;
}
/**
+ * Make an URI for a status.
+ *
+ * @param object $status status object
+ *
+ * @return string URI
+ */
+
+ function makeStatusURI($username, $id)
+ {
+ return 'http://twitter.com/'
+ . $username
+ . '/status/'
+ . $id;
+ }
+
+ /**
* Look up a Profile by profileurl field. Profile::staticGet() was
* not working consistently.
*
@@ -631,6 +703,104 @@ class TwitterStatusFetcher extends ParallelizingDaemon
return true;
}
+
+ const URL = 1;
+ const HASHTAG = 2;
+ const MENTION = 3;
+
+ function linkify($status)
+ {
+ $text = $status->text;
+
+ if (empty($status->entities)) {
+ return $text;
+ }
+
+ // Move all the entities into order so we can
+ // replace them in reverse order and thus
+ // not mess up their indices
+
+ $toReplace = array();
+
+ if (!empty($status->entities->urls)) {
+ foreach ($status->entities->urls as $url) {
+ $toReplace[$url->indices[0]] = array(self::URL, $url);
+ }
+ }
+
+ if (!empty($status->entities->hashtags)) {
+ foreach ($status->entities->hashtags as $hashtag) {
+ $toReplace[$hashtag->indices[0]] = array(self::HASHTAG, $hashtag);
+ }
+ }
+
+ if (!empty($status->entities->user_mentions)) {
+ foreach ($status->entities->user_mentions as $mention) {
+ $toReplace[$mention->indices[0]] = array(self::MENTION, $mention);
+ }
+ }
+
+ // sort in reverse order by key
+
+ krsort($toReplace);
+
+ foreach ($toReplace as $part) {
+ list($type, $object) = $part;
+ switch($type) {
+ case self::URL:
+ $linkText = $this->makeUrlLink($object);
+ break;
+ case self::HASHTAG:
+ $linkText = $this->makeHashtagLink($object);
+ break;
+ case self::MENTION:
+ $linkText = $this->makeMentionLink($object);
+ break;
+ default:
+ continue;
+ }
+ $text = mb_substr($text, 0, $object->indices[0]) . $linkText . mb_substr($text, $object->indices[1]);
+ }
+ return $text;
+ }
+
+ function makeUrlLink($object)
+ {
+ return "<a href='{$object->url}' class='extlink'>{$object->url}</a>";
+ }
+
+ function makeHashtagLink($object)
+ {
+ return "#<a href='https://twitter.com/search?q=%23{$object->text}' class='hashtag'>{$object->text}</a>";
+ }
+
+ function makeMentionLink($object)
+ {
+ return "@<a href='http://twitter.com/{$object->screen_name}' title='{$object->name}'>{$object->screen_name}</a>";
+ }
+
+ function saveStatusMentions($notice, $status)
+ {
+ $mentions = array();
+
+ if (empty($status->entities) || empty($status->entities->user_mentions)) {
+ return;
+ }
+
+ foreach ($status->entities->user_mentions as $mention) {
+ $flink = Foreign_link::getByForeignID($mention->id, TWITTER_SERVICE);
+ if (!empty($flink)) {
+ $user = User::staticGet('id', $flink->user_id);
+ if (!empty($user)) {
+ $reply = new Reply();
+ $reply->notice_id = $notice->id;
+ $reply->profile_id = $user->id;
+ common_log(LOG_INFO, __METHOD__ . ": saving reply: notice {$notice->id} to profile {$user->id}");
+ $id = $reply->insert();
+ }
+ }
+ }
+ }
}
$id = null;
diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php
index 306ba2442..90b0f0f14 100644
--- a/plugins/TwitterBridge/twitter.php
+++ b/plugins/TwitterBridge/twitter.php
@@ -23,7 +23,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
-require_once INSTALLDIR . '/plugins/TwitterBridge/twitterbasicauthclient.php';
require_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php';
function add_twitter_user($twitter_id, $screen_name)
@@ -115,9 +114,12 @@ function is_twitter_bound($notice, $flink) {
// Check to see if notice should go to Twitter
if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) {
- // If it's not a Twitter-style reply, or if the user WANTS to send replies.
+ // If it's not a Twitter-style reply, or if the user WANTS to send replies,
+ // or if it's in reply to a twitter notice
+
if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
- ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
+ ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) ||
+ is_twitter_notice($notice->reply_to)) {
return true;
}
}
@@ -125,22 +127,64 @@ function is_twitter_bound($notice, $flink) {
return false;
}
+function is_twitter_notice($id)
+{
+ $n2s = Notice_to_status::staticGet('notice_id', $id);
+
+ return (!empty($n2s));
+}
+
function broadcast_twitter($notice)
{
$flink = Foreign_link::getByUserID($notice->profile_id,
TWITTER_SERVICE);
- if (is_twitter_bound($notice, $flink)) {
- if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
+ // Don't bother with basic auth, since it's no longer allowed
+
+ if (!empty($flink) && TwitterOAuthClient::isPackedToken($flink->credentials)) {
+ if (!empty($notice->repeat_of) && is_twitter_notice($notice->repeat_of)) {
+ $retweet = retweet_notice($flink, Notice::staticGet('id', $notice->repeat_of));
+ if (!empty($retweet)) {
+ Notice_to_status::saveNew($notice->id, $retweet->id);
+ }
+ } else if (is_twitter_bound($notice, $flink)) {
return broadcast_oauth($notice, $flink);
- } else {
- return broadcast_basicauth($notice, $flink);
}
}
return true;
}
+function retweet_notice($flink, $notice)
+{
+ $token = TwitterOAuthClient::unpackToken($flink->credentials);
+ $client = new TwitterOAuthClient($token->key, $token->secret);
+
+ $id = twitter_status_id($notice);
+
+ if (empty($id)) {
+ common_log(LOG_WARNING, "Trying to retweet notice {$notice->id} with no known status id.");
+ return null;
+ }
+
+ try {
+ $status = $client->statusesRetweet($id);
+ return $status;
+ } catch (OAuthClientException $e) {
+ return process_error($e, $flink, $notice);
+ }
+}
+
+function twitter_status_id($notice)
+{
+ $n2s = Notice_to_status::staticGet('notice_id', $notice->id);
+ if (empty($n2s)) {
+ return null;
+ } else {
+ return $n2s->status_id;
+ }
+}
+
/**
* Pull any extra information from a notice that we should transfer over
* to Twitter beyond the notice text itself.
@@ -156,10 +200,13 @@ function twitter_update_params($notice)
$params['lat'] = $notice->lat;
$params['long'] = $notice->lon;
}
+ if (!empty($notice->reply_to) && is_twitter_notice($notice->reply_to)) {
+ $reply = Notice::staticGet('id', $notice->reply_to);
+ $params['in_reply_to_status_id'] = twitter_status_id($reply);
+ }
return $params;
}
-
function broadcast_oauth($notice, $flink) {
$user = $flink->getUser();
$statustxt = format_status($notice);
@@ -171,6 +218,9 @@ function broadcast_oauth($notice, $flink) {
try {
$status = $client->statusesUpdate($statustxt, $params);
+ if (!empty($status)) {
+ Notice_to_status::saveNew($notice->id, $status->id);
+ }
} catch (OAuthClientException $e) {
return process_error($e, $flink, $notice);
}
@@ -204,52 +254,6 @@ function broadcast_oauth($notice, $flink) {
return true;
}
-function broadcast_basicauth($notice, $flink)
-{
- $user = $flink->getUser();
-
- $statustxt = format_status($notice);
- $params = twitter_update_params($notice);
-
- $client = new TwitterBasicAuthClient($flink);
- $status = null;
-
- try {
- $status = $client->statusesUpdate($statustxt, $params);
- } catch (BasicAuthException $e) {
- return process_error($e, $flink, $notice);
- }
-
- if (empty($status)) {
-
- $errmsg = sprintf('Twitter bridge - No data returned by Twitter API when ' .
- 'trying to post notice %d for %s (user id %d).',
- $notice->id,
- $user->nickname,
- $user->id);
-
- common_log(LOG_WARNING, $errmsg);
-
- $errmsg = sprintf('No data returned by Twitter API when ' .
- 'trying to post notice %d for %s (user id %d).',
- $notice->id,
- $user->nickname,
- $user->id);
- common_log(LOG_WARNING, $errmsg);
- return false;
- }
-
- $msg = sprintf('Twitter bridge - posted notice %d to Twitter using ' .
- 'HTTP basic auth for User %s (user id %d).',
- $notice->id,
- $user->nickname,
- $user->id);
-
- common_log(LOG_INFO, $msg);
-
- return true;
-}
-
function process_error($e, $flink, $notice)
{
$user = $flink->getUser();
diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php
deleted file mode 100644
index 2c18c9469..000000000
--- a/plugins/TwitterBridge/twitterbasicauthclient.php
+++ /dev/null
@@ -1,229 +0,0 @@
-<?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);
-}
-
-/**
- * General Exception wrapper for HTTP basic auth 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 BasicAuthException 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 mixed $params optional other parameters to pass to Twitter,
- * as defined. For back-compatibility, if an int
- * is passed we'll consider it a reply-to ID.
- *
- * @return mixed the status
- */
- function statusesUpdate($status, $in_reply_to_status_id = null)
- {
- $url = 'https://twitter.com/statuses/update.json';
- if (is_numeric($params)) {
- $params = array('in_reply_to_status_id' => intval($params));
- }
- $params['status'] = $status;
- $params['source'] = common_config('integration', 'source');
- $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 an HTTP request
- *
- * @param string $url Where to make the request
- * @param array $params post parameters
- *
- * @return mixed the request
- * @throws BasicAuthException
- */
- function httpRequest($url, $params = null, $auth = true)
- {
- $request = HTTPClient::start();
- $request->setConfig(array(
- 'follow_redirects' => true,
- 'connect_timeout' => 120,
- 'timeout' => 120,
- 'ssl_verify_peer' => false,
- 'ssl_verify_host' => false
- ));
-
- if ($auth) {
- $request->setAuth($this->screen_name, $this->password);
- }
-
- if (isset($params)) {
- // Twitter is strict about accepting invalid "Expect" headers
- $headers = array('Expect:');
- $response = $request->post($url, $headers, $params);
- } else {
- $response = $request->get($url);
- }
-
- $code = $response->getStatus();
-
- if ($code < 200 || $code >= 400) {
- throw new BasicAuthException($response->getBody(), $code);
- }
-
- return $response->getBody();
- }
-
-}
diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php
index d895d8c73..876e30425 100644
--- a/plugins/TwitterBridge/twitteroauthclient.php
+++ b/plugins/TwitterBridge/twitteroauthclient.php
@@ -188,7 +188,7 @@ class TwitterOAuthClient extends OAuthClient
}
/**
- * Calls Twitter's /statuses/friends_timeline API method
+ * Calls Twitter's /statuses/home_timeline API method
*
* @param int $since_id show statuses after this id
* @param int $max_id show statuses before this id
@@ -197,22 +197,28 @@ class TwitterOAuthClient extends OAuthClient
*
* @return mixed an array of statuses
*/
- function statusesFriendsTimeline($since_id = null, $max_id = null,
- $cnt = null, $page = null)
+ function statusesHomeTimeline($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);
+ $url = 'https://twitter.com/statuses/home_timeline.json';
- if (!empty($qry)) {
- $url .= "?$qry";
+ $params = array('include_entities' => 'true');
+
+ if (!empty($since_id)) {
+ $params['since_id'] = $since_id;
+ }
+ if (!empty($max_id)) {
+ $params['max_id'] = $max_id;
+ }
+ if (!empty($cnt)) {
+ $params['count'] = $cnt;
+ }
+ if (!empty($page)) {
+ $params['page'] = $page;
}
- $response = $this->oAuthGet($url);
+ $response = $this->oAuthGet($url, $params);
$statuses = json_decode($response);
return $statuses;
}
@@ -232,17 +238,25 @@ class TwitterOAuthClient extends OAuthClient
{
$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);
+ $params = array();
- if (!empty($qry)) {
- $url .= "?$qry";
+ if (!empty($id)) {
+ $params['id'] = $id;
}
- $response = $this->oAuthGet($url);
+ if (!empty($user_id)) {
+ $params['user_id'] = $user_id;
+ }
+
+ if (!empty($screen_name)) {
+ $params['screen_name'] = $screen_name;
+ }
+
+ if (!empty($page)) {
+ $params['page'] = $page;
+ }
+
+ $response = $this->oAuthGet($url, $params);
$friends = json_decode($response);
return $friends;
}
@@ -262,19 +276,90 @@ class TwitterOAuthClient extends OAuthClient
{
$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);
+ $params = array();
+
+ if (!empty($id)) {
+ $params['id'] = $id;
+ }
+
+ if (!empty($user_id)) {
+ $params['user_id'] = $user_id;
+ }
+
+ if (!empty($screen_name)) {
+ $params['screen_name'] = $screen_name;
+ }
- if (!empty($qry)) {
- $url .= "?$qry";
+ if (!empty($page)) {
+ $params['page'] = $page;
}
- $response = $this->oAuthGet($url);
+ $response = $this->oAuthGet($url, $params);
$ids = json_decode($response);
return $ids;
}
+ /**
+ * Calls Twitter's /statuses/retweet/id.json API method
+ *
+ * @param int $id id of the notice to retweet
+ *
+ * @return retweeted status
+ */
+
+ function statusesRetweet($id)
+ {
+ $url = "http://api.twitter.com/1/statuses/retweet/$id.json";
+ $response = $this->oAuthPost($url);
+ $status = json_decode($response);
+ return $status;
+ }
+
+ /**
+ * Calls Twitter's /favorites/create API method
+ *
+ * @param int $id ID of the status to favorite
+ *
+ * @return object faved status
+ */
+
+ function favoritesCreate($id)
+ {
+ $url = "http://api.twitter.com/1/favorites/create/$id.json";
+ $response = $this->oAuthPost($url);
+ $status = json_decode($response);
+ return $status;
+ }
+
+ /**
+ * Calls Twitter's /favorites/destroy API method
+ *
+ * @param int $id ID of the status to unfavorite
+ *
+ * @return object unfaved status
+ */
+
+ function favoritesDestroy($id)
+ {
+ $url = "http://api.twitter.com/1/favorites/destroy/$id.json";
+ $response = $this->oAuthPost($url);
+ $status = json_decode($response);
+ return $status;
+ }
+
+ /**
+ * Calls Twitter's /statuses/destroy API method
+ *
+ * @param int $id ID of the status to destroy
+ *
+ * @return object destroyed
+ */
+
+ function statusesDestroy($id)
+ {
+ $url = "http://api.twitter.com/1/statuses/destroy/$id.json";
+ $response = $this->oAuthPost($url);
+ $status = json_decode($response);
+ return $status;
+ }
}