forked from GNUsocial/gnu-social
Merge branch '0.9.x' into 1.0.x
This commit is contained in:
commit
b716d01a41
@ -55,7 +55,7 @@
|
||||
Yes
|
||||
|
||||
@param status (Required) The URL-encoded text of the status update.
|
||||
@param source (Optional) The source of the status.
|
||||
@param source (Optional) The source application name, if using HTTP authentication or an anonymous OAuth consumer.
|
||||
@param in_reply_to_status_id (Optional) The ID of an existing status that the update is in reply to.
|
||||
@param lat (Optional) The latitude the status refers to.
|
||||
@param long (Optional) The longitude the status refers to.
|
||||
@ -67,7 +67,7 @@
|
||||
@subsection usagenotes Usage notes
|
||||
|
||||
@li The URL pattern is relative to the @ref apiroot.
|
||||
@li If the @e source parameter is not supplied the source of the status will default to 'api'.
|
||||
@li If the @e source parameter is not supplied the source of the status will default to 'api'. When authenticated via a registered OAuth application, the application's registered name and URL will always override the source parameter.
|
||||
@li The XML response uses <a href="http://georss.org/Main_Page">GeoRSS</a>
|
||||
to encode the latitude and longitude (see example response below <georss:point>).
|
||||
@li Data uploaded via the @e media parameter should be multipart/form-data encoded.
|
||||
|
@ -142,7 +142,7 @@ class InviteAction extends CurrentUserDesignAction
|
||||
$this->elementStart('ul');
|
||||
foreach ($this->already as $other) {
|
||||
// TRANS: Used as list item for already subscribed users (%1$s is nickname, %2$s is e-mail address).
|
||||
$this->element('li', null, sprintf(_('%1$s (%2$s)'), $other->nickname, $other->email));
|
||||
$this->element('li', null, sprintf(_m('INVITE','%1$s (%2$s)'), $other->nickname, $other->email));
|
||||
}
|
||||
$this->elementEnd('ul');
|
||||
}
|
||||
@ -156,7 +156,7 @@ class InviteAction extends CurrentUserDesignAction
|
||||
$this->elementStart('ul');
|
||||
foreach ($this->subbed as $other) {
|
||||
// TRANS: Used as list item for already registered people (%1$s is nickname, %2$s is e-mail address).
|
||||
$this->element('li', null, sprintf(_('%1$s (%2$s)'), $other->nickname, $other->email));
|
||||
$this->element('li', null, sprintf(_m('INVITE','%1$s (%2$s)'), $other->nickname, $other->email));
|
||||
}
|
||||
$this->elementEnd('ul');
|
||||
}
|
||||
|
@ -79,11 +79,7 @@ class OembedAction extends Action
|
||||
if (empty($profile)) {
|
||||
$this->serverError(_('Notice has no profile.'), 500);
|
||||
}
|
||||
if (!empty($profile->fullname)) {
|
||||
$authorname = $profile->fullname . ' (' . $profile->nickname . ')';
|
||||
} else {
|
||||
$authorname = $profile->nickname;
|
||||
}
|
||||
$authorname = $profile->getFancyName();
|
||||
$oembed['title'] = sprintf(_('%1$s\'s status on %2$s'),
|
||||
$authorname,
|
||||
common_exact_date($notice->created));
|
||||
|
@ -68,12 +68,7 @@ class ShowgroupAction extends GroupDesignAction
|
||||
*/
|
||||
function title()
|
||||
{
|
||||
if (!empty($this->group->fullname)) {
|
||||
// @todo FIXME: Needs proper i18n. Maybe use a generic method for this?
|
||||
$base = $this->group->fullname . ' (' . $this->group->nickname . ')';
|
||||
} else {
|
||||
$base = $this->group->nickname;
|
||||
}
|
||||
$base = $this->group->getFancyName();
|
||||
|
||||
if ($this->page == 1) {
|
||||
// TRANS: Page title for first group page. %s is a group name.
|
||||
|
@ -167,11 +167,7 @@ class ShownoticeAction extends OwnerDesignAction
|
||||
|
||||
function title()
|
||||
{
|
||||
if (!empty($this->profile->fullname)) {
|
||||
$base = $this->profile->fullname . ' (' . $this->profile->nickname . ')';
|
||||
} else {
|
||||
$base = $this->profile->nickname;
|
||||
}
|
||||
$base = $this->profile->getFancyName();
|
||||
|
||||
return sprintf(_('%1$s\'s status on %2$s'),
|
||||
$base,
|
||||
|
@ -66,17 +66,19 @@ class ShowstreamAction extends ProfileAction
|
||||
$base = $this->profile->getFancyName();
|
||||
if (!empty($this->tag)) {
|
||||
if ($this->page == 1) {
|
||||
// TRANS: Page title showing tagged notices in one user's stream. Param 1 is the username, 2 is the hash tag.
|
||||
// TRANS: Page title showing tagged notices in one user's stream. %1$s is the username, %2$s is the hash tag.
|
||||
return sprintf(_('%1$s tagged %2$s'), $base, $this->tag);
|
||||
} else {
|
||||
// TRANS: Page title showing tagged notices in one user's stream. Param 1 is the username, 2 is the hash tag, 3 is the page number.
|
||||
// TRANS: Page title showing tagged notices in one user's stream.
|
||||
// TRANS: %1$s is the username, %2$s is the hash tag, %1$d is the page number.
|
||||
return sprintf(_('%1$s tagged %2$s, page %3$d'), $base, $this->tag, $this->page);
|
||||
}
|
||||
} else {
|
||||
if ($this->page == 1) {
|
||||
return $base;
|
||||
} else {
|
||||
// TRANS: Extended page title showing tagged notices in one user's stream. Param 1 is the username, param 2 is the page number.
|
||||
// TRANS: Extended page title showing tagged notices in one user's stream.
|
||||
// TRANS: %1$s is the username, %2$d is the page number.
|
||||
return sprintf(_('%1$s, page %2$d'),
|
||||
$base,
|
||||
$this->page);
|
||||
@ -120,6 +122,8 @@ class ShowstreamAction extends ProfileAction
|
||||
common_local_url('userrss',
|
||||
array('nickname' => $this->user->nickname,
|
||||
'tag' => $this->tag)),
|
||||
// TRANS: Title for link to notice feed.
|
||||
// TRANS: %1$s is a user nickname, %2$s is a hashtag.
|
||||
sprintf(_('Notice feed for %1$s tagged %2$s (RSS 1.0)'),
|
||||
$this->user->nickname, $this->tag)));
|
||||
}
|
||||
@ -127,6 +131,8 @@ class ShowstreamAction extends ProfileAction
|
||||
return array(new Feed(Feed::RSS1,
|
||||
common_local_url('userrss',
|
||||
array('nickname' => $this->user->nickname)),
|
||||
// TRANS: Title for link to notice feed.
|
||||
// TRANS: %s is a user nickname.
|
||||
sprintf(_('Notice feed for %s (RSS 1.0)'),
|
||||
$this->user->nickname)),
|
||||
new Feed(Feed::RSS2,
|
||||
@ -134,6 +140,8 @@ class ShowstreamAction extends ProfileAction
|
||||
array(
|
||||
'id' => $this->user->id,
|
||||
'format' => 'rss')),
|
||||
// TRANS: Title for link to notice feed.
|
||||
// TRANS: %s is a user nickname.
|
||||
sprintf(_('Notice feed for %s (RSS 2.0)'),
|
||||
$this->user->nickname)),
|
||||
new Feed(Feed::ATOM,
|
||||
@ -146,6 +154,8 @@ class ShowstreamAction extends ProfileAction
|
||||
new Feed(Feed::FOAF,
|
||||
common_local_url('foaf', array('nickname' =>
|
||||
$this->user->nickname)),
|
||||
// TRANS: Title for link to notice feed. FOAF stands for Friend of a Friend.
|
||||
// TRANS: More information at http://www.foaf-project.org. %s is a user nickname.
|
||||
sprintf(_('FOAF for %s'), $this->user->nickname)));
|
||||
}
|
||||
|
||||
@ -191,17 +201,23 @@ class ShowstreamAction extends ProfileAction
|
||||
|
||||
function showEmptyListMessage()
|
||||
{
|
||||
$message = sprintf(_('This is the timeline for %1$s but %2$s hasn\'t posted anything yet.'), $this->user->nickname, $this->user->nickname) . ' ';
|
||||
// TRANS: First sentence of empty list message for a stream. $1%s is a user nickname.
|
||||
$message = sprintf(_('This is the timeline for %1$s, but %1$s hasn\'t posted anything yet.'), $this->user->nickname) . ' ';
|
||||
|
||||
if (common_logged_in()) {
|
||||
$current_user = common_current_user();
|
||||
if ($this->user->id === $current_user->id) {
|
||||
// TRANS: Second sentence of empty list message for a stream for the user themselves.
|
||||
$message .= _('Seen anything interesting recently? You haven\'t posted any notices yet, now would be a good time to start :)');
|
||||
} else {
|
||||
// TRANS: Second sentence of empty list message for a non-self stream. %1$s is a user nickname, %2$s is a part of a URL.
|
||||
// TRANS: This message contains a Markdown link. Keep "](" together.
|
||||
$message .= sprintf(_('You can try to nudge %1$s or [post something to them](%%%%action.newnotice%%%%?status_textarea=%2$s).'), $this->user->nickname, '@' . $this->user->nickname);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TRANS: Second sentence of empty message for anonymous users. %s is a user nickname.
|
||||
// TRANS: This message contains a Markdown link. Keep "](" together.
|
||||
$message .= sprintf(_('Why not [register an account](%%%%action.register%%%%) and then nudge %s or post a notice to them.'), $this->user->nickname);
|
||||
}
|
||||
|
||||
@ -237,11 +253,15 @@ class ShowstreamAction extends ProfileAction
|
||||
function showAnonymousMessage()
|
||||
{
|
||||
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
|
||||
// TRANS: Announcement for anonymous users showing a stream if site registrations are open.
|
||||
// TRANS: This message contains a Markdown link. Keep "](" together.
|
||||
$m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [StatusNet](http://status.net/) tool. ' .
|
||||
'[Join now](%%%%action.register%%%%) to follow **%s**\'s notices and many more! ([Read more](%%%%doc.help%%%%))'),
|
||||
$this->user->nickname, $this->user->nickname);
|
||||
} else {
|
||||
// TRANS: Announcement for anonymous users showing a stream if site registrations are closed or invite only.
|
||||
// TRANS: This message contains a Markdown link. Keep "](" together.
|
||||
$m = sprintf(_('**%s** has an account on %%%%site.name%%%%, a [micro-blogging](http://en.wikipedia.org/wiki/Micro-blogging) service ' .
|
||||
'based on the Free Software [StatusNet](http://status.net/) tool. '),
|
||||
$this->user->nickname, $this->user->nickname);
|
||||
@ -281,7 +301,6 @@ class ProfileNoticeListItem extends DoFollowListItem
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
function showRepeat()
|
||||
{
|
||||
if (!empty($this->repeat)) {
|
||||
@ -292,13 +311,14 @@ class ProfileNoticeListItem extends DoFollowListItem
|
||||
'class' => 'url');
|
||||
|
||||
if (!empty($this->profile->fullname)) {
|
||||
$attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ')';
|
||||
$attrs['title'] = $this->getFancyName();
|
||||
}
|
||||
|
||||
$this->out->elementStart('span', 'repeat');
|
||||
|
||||
$text_link = XMLStringer::estring('a', $attrs, $this->profile->nickname);
|
||||
|
||||
// TRANS: Link to the author of a repeated notice. %s is a linked nickname.
|
||||
$this->out->raw(sprintf(_('Repeat of %s'), $text_link));
|
||||
|
||||
$this->out->elementEnd('span');
|
||||
|
@ -161,7 +161,7 @@ class Profile extends Memcached_DataObject
|
||||
{
|
||||
if ($this->fullname) {
|
||||
// TRANS: Full name of a profile or group followed by nickname in parens
|
||||
return sprintf(_('%1$s (%2$s)'), $this->fullname, $this->nickname);
|
||||
return sprintf(_m('FANCYNAME','%1$s (%2$s)'), $this->fullname, $this->nickname);
|
||||
} else {
|
||||
return $this->nickname;
|
||||
}
|
||||
|
@ -234,6 +234,22 @@ class User_group extends Memcached_DataObject
|
||||
return ($this->fullname) ? $this->fullname : $this->nickname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full name (if filled) with nickname as a parenthetical, or the nickname alone
|
||||
* if no fullname is provided.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function getFancyName()
|
||||
{
|
||||
if ($this->fullname) {
|
||||
// TRANS: Full name of a profile or group followed by nickname in parens
|
||||
return sprintf(_m('FANCYNAME','%1$s (%2$s)'), $this->fullname, $this->nickname);
|
||||
} else {
|
||||
return $this->nickname;
|
||||
}
|
||||
}
|
||||
|
||||
function getAliases()
|
||||
{
|
||||
$aliases = array();
|
||||
|
@ -873,16 +873,17 @@ class Action extends HTMLOutputter // lawsuit
|
||||
// TRANS: Secondary navigation menu option leading to privacy policy.
|
||||
_('Privacy'));
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'source')),
|
||||
// TRANS: Secondary navigation menu option.
|
||||
// TRANS: Secondary navigation menu option. Leads to information about StatusNet and its license.
|
||||
_('Source'));
|
||||
$this->menuItem(common_local_url('version'),
|
||||
// TRANS: Secondary navigation menu option leading to version information on the StatusNet site.
|
||||
_('Version'));
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'contact')),
|
||||
// TRANS: Secondary navigation menu option leading to contact information on the StatusNet site.
|
||||
// TRANS: Secondary navigation menu option leading to e-mail contact information on the
|
||||
// TRANS: StatusNet site, where to report bugs, ...
|
||||
_('Contact'));
|
||||
$this->menuItem(common_local_url('doc', array('title' => 'badge')),
|
||||
// TRANS: Secondary navigation menu option.
|
||||
// TRANS: Secondary navigation menu option. Leads to information about embedding a timeline widget.
|
||||
_('Badge'));
|
||||
Event::handle('EndSecondaryNav', array($this));
|
||||
}
|
||||
|
@ -423,7 +423,7 @@ class WhoisCommand extends Command
|
||||
|
||||
// TRANS: Whois output.
|
||||
// TRANS: %1$s nickname of the queried user, %2$s is their profile URL.
|
||||
$whois = sprintf(_("%1\$s (%2\$s)"), $recipient->nickname,
|
||||
$whois = sprintf(_m('WHOIS',"%1\$s (%2\$s)"), $recipient->nickname,
|
||||
$recipient->profileurl);
|
||||
if ($recipient->fullname) {
|
||||
// TRANS: Whois output. %s is the full name of the queried user.
|
||||
|
@ -593,6 +593,10 @@ function mail_notify_fave($other, $user, $notice)
|
||||
}
|
||||
|
||||
$profile = $user->getProfile();
|
||||
if ($other->hasBlocked($profile)) {
|
||||
// If the author has blocked us, don't spam them with a notification.
|
||||
return;
|
||||
}
|
||||
|
||||
$bestname = $profile->getBestName();
|
||||
|
||||
|
@ -306,7 +306,7 @@ class NoticeListItem extends Widget
|
||||
$attrs = array('href' => $this->profile->profileurl,
|
||||
'class' => 'url');
|
||||
if (!empty($this->profile->fullname)) {
|
||||
$attrs['title'] = $this->profile->fullname . ' (' . $this->profile->nickname . ')';
|
||||
$attrs['title'] = $this->profile->getFancyName();
|
||||
}
|
||||
$this->out->elementStart('a', $attrs);
|
||||
$this->showAvatar();
|
||||
|
@ -87,8 +87,11 @@ class PersonalGroupNav extends Widget
|
||||
if ($nickname) {
|
||||
$user = User::staticGet('nickname', $nickname);
|
||||
$user_profile = $user->getProfile();
|
||||
$name = $user_profile->getBestName();
|
||||
} else {
|
||||
// @fixme can this happen? is this valid?
|
||||
$user_profile = false;
|
||||
$name = $nickname;
|
||||
}
|
||||
|
||||
$this->out->elementStart('ul', array('class' => 'nav'));
|
||||
@ -97,22 +100,22 @@ class PersonalGroupNav extends Widget
|
||||
$this->out->menuItem(common_local_url('all', array('nickname' =>
|
||||
$nickname)),
|
||||
_('Personal'),
|
||||
sprintf(_('%s and friends'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
|
||||
sprintf(_('%s and friends'), $name),
|
||||
$action == 'all', 'nav_timeline_personal');
|
||||
$this->out->menuItem(common_local_url('replies', array('nickname' =>
|
||||
$nickname)),
|
||||
_('Replies'),
|
||||
sprintf(_('Replies to %s'), (($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname)),
|
||||
sprintf(_('Replies to %s'), $name),
|
||||
$action == 'replies', 'nav_timeline_replies');
|
||||
$this->out->menuItem(common_local_url('showstream', array('nickname' =>
|
||||
$nickname)),
|
||||
_('Profile'),
|
||||
($user_profile && $user_profile->fullname) ? $user_profile->fullname : $nickname,
|
||||
$name,
|
||||
$action == 'showstream', 'nav_profile');
|
||||
$this->out->menuItem(common_local_url('showfavorites', array('nickname' =>
|
||||
$nickname)),
|
||||
_('Favorites'),
|
||||
sprintf(_('%s\'s favorite notices'), ($user_profile) ? $user_profile->getBestName() : _('User')),
|
||||
sprintf(_('%s\'s favorite notices'), ($user_profile) ? $name : _('User')),
|
||||
$action == 'showfavorites', 'nav_timeline_favorites');
|
||||
|
||||
$cur = common_current_user();
|
||||
|
@ -1038,7 +1038,7 @@ function common_group_link($sender_id, $nickname)
|
||||
$attrs = array('href' => $group->permalink(),
|
||||
'class' => 'url');
|
||||
if (!empty($group->fullname)) {
|
||||
$attrs['title'] = $group->fullname . ' (' . $group->nickname . ')';
|
||||
$attrs['title'] = $group->getFancyName();
|
||||
}
|
||||
$xs = new XMLStringer();
|
||||
$xs->elementStart('span', 'vcard');
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -41,12 +41,7 @@ class GroupFavoritedAction extends ShowgroupAction
|
||||
*/
|
||||
function title()
|
||||
{
|
||||
if (!empty($this->group->fullname)) {
|
||||
// @todo Create a core method to create this properly. i18n issue.
|
||||
$base = $this->group->fullname . ' (' . $this->group->nickname . ')';
|
||||
} else {
|
||||
$base = $this->group->nickname;
|
||||
}
|
||||
$base = $this->group->getFancyName();
|
||||
|
||||
if ($this->page == 1) {
|
||||
// TRANS: %s is a group name.
|
||||
|
@ -61,12 +61,7 @@ class AllmapAction extends MapAction
|
||||
|
||||
function title()
|
||||
{
|
||||
if (!empty($this->profile->fullname)) {
|
||||
// @todo FIXME: Bad i18n. Should be "%1$s (%2$s)".
|
||||
$base = $this->profile->fullname . ' (' . $this->user->nickname . ') ';
|
||||
} else {
|
||||
$base = $this->user->nickname;
|
||||
}
|
||||
$base = $this->profile->getFancyName();
|
||||
|
||||
if ($this->page == 1) {
|
||||
// TRANS: Page title.
|
||||
|
@ -58,12 +58,7 @@ class UsermapAction extends MapAction
|
||||
|
||||
function title()
|
||||
{
|
||||
if (!empty($this->profile->fullname)) {
|
||||
// @todo FIXME: Bad i18n. Should be '%1$s (%2$s)'
|
||||
$base = $this->profile->fullname . ' (' . $this->user->nickname . ')';
|
||||
} else {
|
||||
$base = $this->user->nickname;
|
||||
}
|
||||
$base = $this->profile->getFancyName();
|
||||
|
||||
if ($this->page == 1) {
|
||||
// @todo CHECKME: inconsisten with paged variant below. " map" missing.
|
||||
|
@ -150,10 +150,10 @@ class Ostatus_profile extends Managed_DataObject
|
||||
} else if ($this->group_id && !$this->profile_id) {
|
||||
return true;
|
||||
} else if ($this->group_id && $this->profile_id) {
|
||||
// TRANS: Server exception.
|
||||
// TRANS: Server exception. %s is a URI.
|
||||
throw new ServerException(sprintf(_m('Invalid ostatus_profile state: both group and profile IDs set for %s.'),$this->uri));
|
||||
} else {
|
||||
// TRANS: Server exception.
|
||||
// TRANS: Server exception. %s is a URI.
|
||||
throw new ServerException(sprintf(_m('Invalid ostatus_profile state: both group and profile IDs empty for %s.'),$this->uri));
|
||||
}
|
||||
}
|
||||
@ -367,6 +367,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
} else if ($feed->localName == 'rss') { // @fixme check namespace
|
||||
$this->processRssFeed($feed, $source);
|
||||
} else {
|
||||
// TRANS: Exception.
|
||||
throw new Exception(_m('Unknown feed format.'));
|
||||
}
|
||||
}
|
||||
@ -390,6 +391,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
$channels = $rss->getElementsByTagName('channel');
|
||||
|
||||
if ($channels->length == 0) {
|
||||
// TRANS: Exception.
|
||||
throw new Exception(_m('RSS feed without a channel.'));
|
||||
} else if ($channels->length > 1) {
|
||||
common_log(LOG_WARNING, __METHOD__ . ": more than one channel in an RSS feed");
|
||||
@ -517,7 +519,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
$sourceContent = $note->title;
|
||||
} else {
|
||||
// @fixme fetch from $sourceUrl?
|
||||
// TRANS: Client exception. %s is a source URL.
|
||||
// TRANS: Client exception. %s is a source URI.
|
||||
throw new ClientException(sprintf(_m('No content for notice %s.'),$sourceUri));
|
||||
}
|
||||
|
||||
@ -551,7 +553,8 @@ class Ostatus_profile extends Managed_DataObject
|
||||
// so we can fold-out the full version inline.
|
||||
|
||||
// @fixme I18N this tooltip will be saved with the site's default language
|
||||
// TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime this will usually be replaced with localized text from StatusNet core messages.
|
||||
// TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
|
||||
// TRANS: this will usually be replaced with localised text from StatusNet core messages.
|
||||
$showMoreText = _m('Show more');
|
||||
$attachUrl = common_local_url('attachment',
|
||||
array('attachment' => $attachment->id));
|
||||
@ -802,7 +805,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
return self::ensureFeedURL($feedurl, $hints);
|
||||
}
|
||||
|
||||
// TRANS: Exception.
|
||||
// TRANS: Exception. %s is a URL.
|
||||
throw new Exception(sprintf(_m('Could not find a feed URL for profile page %s.'),$finalUrl));
|
||||
}
|
||||
|
||||
@ -940,6 +943,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
}
|
||||
|
||||
// XXX: make some educated guesses here
|
||||
// TRANS: Feed sub exception.
|
||||
throw new FeedSubException(_m('Can\'t find enough profile information to make a feed.'));
|
||||
}
|
||||
|
||||
@ -999,6 +1003,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
return;
|
||||
}
|
||||
if (!common_valid_http_url($url)) {
|
||||
// TRANS: Server exception. %s is a URL.
|
||||
throw new ServerException(sprintf(_m("Invalid avatar URL %s."), $url));
|
||||
}
|
||||
|
||||
@ -1009,6 +1014,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
}
|
||||
if (!$self) {
|
||||
throw new ServerException(sprintf(
|
||||
// TRANS: Server exception. %s is a URI.
|
||||
_m("Tried to update avatar for unsaved remote profile %s."),
|
||||
$this->uri));
|
||||
}
|
||||
@ -1018,6 +1024,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
$temp_filename = tempnam(sys_get_temp_dir(), 'listener_avatar');
|
||||
try {
|
||||
if (!copy($url, $temp_filename)) {
|
||||
// TRANS: Server exception. %s is a URL.
|
||||
throw new ServerException(sprintf(_m("Unable to fetch avatar from %s."), $url));
|
||||
}
|
||||
|
||||
@ -1300,7 +1307,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
|
||||
$oprofile->profile_id = $profile->insert();
|
||||
if (!$oprofile->profile_id) {
|
||||
// TRANS: Exception.
|
||||
// TRANS: Server exception.
|
||||
throw new ServerException(_m('Can\'t save local profile.'));
|
||||
}
|
||||
} else {
|
||||
@ -1311,7 +1318,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
|
||||
$oprofile->group_id = $group->insert();
|
||||
if (!$oprofile->group_id) {
|
||||
// TRANS: Exception.
|
||||
// TRANS: Server exception.
|
||||
throw new ServerException(_m('Can\'t save local profile.'));
|
||||
}
|
||||
}
|
||||
@ -1319,7 +1326,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
$ok = $oprofile->insert();
|
||||
|
||||
if (!$ok) {
|
||||
// TRANS: Exception.
|
||||
// TRANS: Server exception.
|
||||
throw new ServerException(_m('Can\'t save OStatus profile.'));
|
||||
}
|
||||
|
||||
@ -1758,6 +1765,7 @@ class Ostatus_profile extends Managed_DataObject
|
||||
|
||||
if ($file_id === false) {
|
||||
common_log_db_error($file, "INSERT", __FILE__);
|
||||
// TRANS: Server exception.
|
||||
throw new ServerException(_m('Could not store HTML content of long post as file.'));
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-10-27 23:43+0000\n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -119,13 +119,13 @@ msgstr ""
|
||||
msgid "Attempting to end PuSH subscription for feed with no hub."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:192
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:195
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
|
||||
@ -145,105 +145,113 @@ msgid ""
|
||||
"Activity entry."
|
||||
msgstr ""
|
||||
|
||||
#: classes/Ostatus_profile.php:408
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:409
|
||||
msgid "Unknown feed format."
|
||||
msgstr ""
|
||||
|
||||
#: classes/Ostatus_profile.php:431
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:433
|
||||
msgid "RSS feed without a channel."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Client exception.
|
||||
#: classes/Ostatus_profile.php:476
|
||||
#: classes/Ostatus_profile.php:478
|
||||
msgid "Can't handle that kind of post."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Client exception. %s is a source URL.
|
||||
#: classes/Ostatus_profile.php:559
|
||||
#. TRANS: Client exception. %s is a source URI.
|
||||
#: classes/Ostatus_profile.php:561
|
||||
#, php-format
|
||||
msgid "No content for notice %s."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
|
||||
#: classes/Ostatus_profile.php:592
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
|
||||
#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
|
||||
#: classes/Ostatus_profile.php:596
|
||||
msgid "Show more"
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception. %s is a profile URL.
|
||||
#: classes/Ostatus_profile.php:785
|
||||
#: classes/Ostatus_profile.php:789
|
||||
#, php-format
|
||||
msgid "Could not reach profile page %s."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:843
|
||||
#. TRANS: Exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:847
|
||||
#, php-format
|
||||
msgid "Could not find a feed URL for profile page %s."
|
||||
msgstr ""
|
||||
|
||||
#: classes/Ostatus_profile.php:980
|
||||
#. TRANS: Feed sub exception.
|
||||
#: classes/Ostatus_profile.php:985
|
||||
msgid "Can't find enough profile information to make a feed."
|
||||
msgstr ""
|
||||
|
||||
#: classes/Ostatus_profile.php:1039
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1045
|
||||
#, php-format
|
||||
msgid "Invalid avatar URL %s."
|
||||
msgstr ""
|
||||
|
||||
#: classes/Ostatus_profile.php:1049
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:1056
|
||||
#, php-format
|
||||
msgid "Tried to update avatar for unsaved remote profile %s."
|
||||
msgstr ""
|
||||
|
||||
#: classes/Ostatus_profile.php:1058
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1066
|
||||
#, php-format
|
||||
msgid "Unable to fetch avatar from %s."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1284
|
||||
#: classes/Ostatus_profile.php:1292
|
||||
msgid "Local user can't be referenced as remote."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1289
|
||||
#: classes/Ostatus_profile.php:1297
|
||||
msgid "Local group can't be referenced as remote."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
|
||||
msgid "Can't save local profile."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1360
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1368
|
||||
msgid "Can't save OStatus profile."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
|
||||
#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
|
||||
msgid "Not a valid webfinger address."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1729
|
||||
#: classes/Ostatus_profile.php:1737
|
||||
#, php-format
|
||||
msgid "Couldn't save profile for \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1748
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#, php-format
|
||||
msgid "Couldn't save ostatus_profile for \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#: classes/Ostatus_profile.php:1764
|
||||
#, php-format
|
||||
msgid "Couldn't find a valid profile for \"%s\"."
|
||||
msgstr ""
|
||||
|
||||
#: classes/Ostatus_profile.php:1798
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1807
|
||||
msgid "Could not store HTML content of long post as file."
|
||||
msgstr ""
|
||||
|
||||
|
@ -10,13 +10,13 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - OStatus\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-10-27 23:43+0000\n"
|
||||
"PO-Revision-Date: 2010-10-27 23:47:14+0000\n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
|
||||
"Language-Team: French <http://translatewiki.net/wiki/Portal:fr>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
|
||||
"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: fr\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
|
||||
@ -131,7 +131,7 @@ msgstr ""
|
||||
"Tente d’arrêter l’inscription PuSH à un flux d’information sans "
|
||||
"concentrateur."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:192
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
|
||||
@ -139,7 +139,7 @@ msgstr ""
|
||||
"État invalide du profil OStatus : identifiants à la fois de groupe et de "
|
||||
"profil définis pour « %s »."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:195
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
|
||||
@ -163,111 +163,119 @@ msgstr ""
|
||||
"Type invalide passé à la méthode « Ostatus_profile::notify ». Ce doit être "
|
||||
"une chaîne XML ou une entrée « Activity »."
|
||||
|
||||
#: classes/Ostatus_profile.php:408
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:409
|
||||
msgid "Unknown feed format."
|
||||
msgstr "Format de flux d’information inconnu."
|
||||
|
||||
#: classes/Ostatus_profile.php:431
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:433
|
||||
msgid "RSS feed without a channel."
|
||||
msgstr "Flux RSS sans canal."
|
||||
|
||||
#. TRANS: Client exception.
|
||||
#: classes/Ostatus_profile.php:476
|
||||
#: classes/Ostatus_profile.php:478
|
||||
msgid "Can't handle that kind of post."
|
||||
msgstr "Impossible de gérer cette sorte de publication."
|
||||
|
||||
#. TRANS: Client exception. %s is a source URL.
|
||||
#: classes/Ostatus_profile.php:559
|
||||
#. TRANS: Client exception. %s is a source URI.
|
||||
#: classes/Ostatus_profile.php:561
|
||||
#, php-format
|
||||
msgid "No content for notice %s."
|
||||
msgstr "Aucun contenu dans l’avis « %s »."
|
||||
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
|
||||
#: classes/Ostatus_profile.php:592
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
|
||||
#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
|
||||
#: classes/Ostatus_profile.php:596
|
||||
msgid "Show more"
|
||||
msgstr "Voir davantage"
|
||||
|
||||
#. TRANS: Exception. %s is a profile URL.
|
||||
#: classes/Ostatus_profile.php:785
|
||||
#: classes/Ostatus_profile.php:789
|
||||
#, php-format
|
||||
msgid "Could not reach profile page %s."
|
||||
msgstr "Impossible d’atteindre la page de profil « %s »."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:843
|
||||
#. TRANS: Exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:847
|
||||
#, php-format
|
||||
msgid "Could not find a feed URL for profile page %s."
|
||||
msgstr ""
|
||||
"Impossible de trouver une adresse URL de flux d’information pour la page de "
|
||||
"profil « %s »."
|
||||
|
||||
#: classes/Ostatus_profile.php:980
|
||||
#. TRANS: Feed sub exception.
|
||||
#: classes/Ostatus_profile.php:985
|
||||
msgid "Can't find enough profile information to make a feed."
|
||||
msgstr ""
|
||||
"Impossible de trouver assez d’informations de profil pour créer un flux "
|
||||
"d’information."
|
||||
|
||||
#: classes/Ostatus_profile.php:1039
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1045
|
||||
#, php-format
|
||||
msgid "Invalid avatar URL %s."
|
||||
msgstr "Adresse URL d’avatar « %s » invalide."
|
||||
|
||||
#: classes/Ostatus_profile.php:1049
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:1056
|
||||
#, php-format
|
||||
msgid "Tried to update avatar for unsaved remote profile %s."
|
||||
msgstr ""
|
||||
"Tente de mettre à jour l’avatar associé au profil distant non sauvegardé « %s "
|
||||
"»."
|
||||
|
||||
#: classes/Ostatus_profile.php:1058
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1066
|
||||
#, php-format
|
||||
msgid "Unable to fetch avatar from %s."
|
||||
msgstr "Impossible de récupérer l’avatar depuis « %s »."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1284
|
||||
#: classes/Ostatus_profile.php:1292
|
||||
msgid "Local user can't be referenced as remote."
|
||||
msgstr "L’utilisateur local ne peut être référencé comme distant."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1289
|
||||
#: classes/Ostatus_profile.php:1297
|
||||
msgid "Local group can't be referenced as remote."
|
||||
msgstr "Le groupe local ne peut être référencé comme distant."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
|
||||
msgid "Can't save local profile."
|
||||
msgstr "Impossible de sauvegarder le profil local."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1360
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1368
|
||||
msgid "Can't save OStatus profile."
|
||||
msgstr "Impossible de sauvegarder le profil OStatus."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
|
||||
#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
|
||||
msgid "Not a valid webfinger address."
|
||||
msgstr "Ce n’est pas une adresse « webfinger » valide."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1729
|
||||
#: classes/Ostatus_profile.php:1737
|
||||
#, php-format
|
||||
msgid "Couldn't save profile for \"%s\"."
|
||||
msgstr "Impossible de sauvegarder le profil pour « %s »."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1748
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#, php-format
|
||||
msgid "Couldn't save ostatus_profile for \"%s\"."
|
||||
msgstr "Impossible d’enregistrer le profil OStatus pour « %s »."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#: classes/Ostatus_profile.php:1764
|
||||
#, php-format
|
||||
msgid "Couldn't find a valid profile for \"%s\"."
|
||||
msgstr "Impossible de trouver un profil valide pour « %s »."
|
||||
|
||||
#: classes/Ostatus_profile.php:1798
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1807
|
||||
msgid "Could not store HTML content of long post as file."
|
||||
msgstr ""
|
||||
"Impossible de stocker le contenu HTML d’une longue publication en un fichier."
|
||||
|
@ -9,13 +9,13 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - OStatus\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-10-27 23:43+0000\n"
|
||||
"PO-Revision-Date: 2010-10-27 23:47:15+0000\n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
|
||||
"Language-Team: Interlingua <http://translatewiki.net/wiki/Portal:ia>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
|
||||
"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: ia\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
|
||||
@ -126,14 +126,14 @@ msgstr "Tentativa de comenciar subscription PuSH pro syndication sin centro."
|
||||
msgid "Attempting to end PuSH subscription for feed with no hub."
|
||||
msgstr "Tentativa de terminar subscription PuSH pro syndication sin centro."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:192
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
|
||||
msgstr ""
|
||||
"Stato ostatus_profile invalide: IDs e de gruppo e de profilo definite pro %s."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:195
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
|
||||
@ -156,106 +156,114 @@ msgstr ""
|
||||
"Typo invalide passate a Ostatos_profile::notify. Illo debe esser catena XML "
|
||||
"o entrata Activity."
|
||||
|
||||
#: classes/Ostatus_profile.php:408
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:409
|
||||
msgid "Unknown feed format."
|
||||
msgstr "Formato de syndication incognite."
|
||||
|
||||
#: classes/Ostatus_profile.php:431
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:433
|
||||
msgid "RSS feed without a channel."
|
||||
msgstr "Syndication RSS sin canal."
|
||||
|
||||
#. TRANS: Client exception.
|
||||
#: classes/Ostatus_profile.php:476
|
||||
#: classes/Ostatus_profile.php:478
|
||||
msgid "Can't handle that kind of post."
|
||||
msgstr "Non pote tractar iste typo de message."
|
||||
|
||||
#. TRANS: Client exception. %s is a source URL.
|
||||
#: classes/Ostatus_profile.php:559
|
||||
#. TRANS: Client exception. %s is a source URI.
|
||||
#: classes/Ostatus_profile.php:561
|
||||
#, php-format
|
||||
msgid "No content for notice %s."
|
||||
msgstr "Nulle contento pro nota %s."
|
||||
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
|
||||
#: classes/Ostatus_profile.php:592
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
|
||||
#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
|
||||
#: classes/Ostatus_profile.php:596
|
||||
msgid "Show more"
|
||||
msgstr "Monstrar plus"
|
||||
|
||||
#. TRANS: Exception. %s is a profile URL.
|
||||
#: classes/Ostatus_profile.php:785
|
||||
#: classes/Ostatus_profile.php:789
|
||||
#, php-format
|
||||
msgid "Could not reach profile page %s."
|
||||
msgstr "Non poteva attinger pagina de profilo %s."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:843
|
||||
#. TRANS: Exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:847
|
||||
#, php-format
|
||||
msgid "Could not find a feed URL for profile page %s."
|
||||
msgstr "Non poteva trovar un URL de syndication pro pagina de profilo %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:980
|
||||
#. TRANS: Feed sub exception.
|
||||
#: classes/Ostatus_profile.php:985
|
||||
msgid "Can't find enough profile information to make a feed."
|
||||
msgstr ""
|
||||
"Non pote trovar satis de information de profilo pro facer un syndication."
|
||||
|
||||
#: classes/Ostatus_profile.php:1039
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1045
|
||||
#, php-format
|
||||
msgid "Invalid avatar URL %s."
|
||||
msgstr "URL de avatar %s invalide."
|
||||
|
||||
#: classes/Ostatus_profile.php:1049
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:1056
|
||||
#, php-format
|
||||
msgid "Tried to update avatar for unsaved remote profile %s."
|
||||
msgstr "Tentava actualisar avatar pro profilo remote non salveguardate %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:1058
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1066
|
||||
#, php-format
|
||||
msgid "Unable to fetch avatar from %s."
|
||||
msgstr "Incapace de obtener avatar ab %s."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1284
|
||||
#: classes/Ostatus_profile.php:1292
|
||||
msgid "Local user can't be referenced as remote."
|
||||
msgstr "Usator local non pote esser referentiate como remote."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1289
|
||||
#: classes/Ostatus_profile.php:1297
|
||||
msgid "Local group can't be referenced as remote."
|
||||
msgstr "Gruppo local non pote esser referentiate como remote."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
|
||||
msgid "Can't save local profile."
|
||||
msgstr "Non pote salveguardar profilo local."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1360
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1368
|
||||
msgid "Can't save OStatus profile."
|
||||
msgstr "Non pote salveguardar profilo OStatus."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
|
||||
#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
|
||||
msgid "Not a valid webfinger address."
|
||||
msgstr "Adresse webfinger invalide."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1729
|
||||
#: classes/Ostatus_profile.php:1737
|
||||
#, php-format
|
||||
msgid "Couldn't save profile for \"%s\"."
|
||||
msgstr "Non poteva salveguardar profilo pro \"%s\"."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1748
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#, php-format
|
||||
msgid "Couldn't save ostatus_profile for \"%s\"."
|
||||
msgstr "Non poteva salveguardar osatus_profile pro %s."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#: classes/Ostatus_profile.php:1764
|
||||
#, php-format
|
||||
msgid "Couldn't find a valid profile for \"%s\"."
|
||||
msgstr "Non poteva trovar un profilo valide pro \"%s\"."
|
||||
|
||||
#: classes/Ostatus_profile.php:1798
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1807
|
||||
msgid "Could not store HTML content of long post as file."
|
||||
msgstr "Non poteva immagazinar contento HTML de longe message como file."
|
||||
|
||||
|
@ -9,13 +9,13 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - OStatus\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-10-27 23:43+0000\n"
|
||||
"PO-Revision-Date: 2010-10-27 23:47:15+0000\n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
|
||||
"Language-Team: Macedonian <http://translatewiki.net/wiki/Portal:mk>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
|
||||
"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: mk\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
|
||||
@ -127,7 +127,7 @@ msgid "Attempting to end PuSH subscription for feed with no hub."
|
||||
msgstr ""
|
||||
"Се обидувам да ставам крај на PuSH-претплатата за емитување без средиште."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:192
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
|
||||
@ -135,7 +135,7 @@ msgstr ""
|
||||
"Неважечка ostatus_profile-состојба: назнаките (ID) на групата и профилот се "
|
||||
"наместени за %s."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:195
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
|
||||
@ -159,106 +159,114 @@ msgstr ""
|
||||
"На Ostatus_profile::notify е пренесен неважечки тип. Мора да биде XML-низа "
|
||||
"или ставка во Activity."
|
||||
|
||||
#: classes/Ostatus_profile.php:408
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:409
|
||||
msgid "Unknown feed format."
|
||||
msgstr "Непознат формат на каналско емитување."
|
||||
|
||||
#: classes/Ostatus_profile.php:431
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:433
|
||||
msgid "RSS feed without a channel."
|
||||
msgstr "RSS-емитување без канал."
|
||||
|
||||
#. TRANS: Client exception.
|
||||
#: classes/Ostatus_profile.php:476
|
||||
#: classes/Ostatus_profile.php:478
|
||||
msgid "Can't handle that kind of post."
|
||||
msgstr "Не можам да работам со таква објава."
|
||||
|
||||
#. TRANS: Client exception. %s is a source URL.
|
||||
#: classes/Ostatus_profile.php:559
|
||||
#. TRANS: Client exception. %s is a source URI.
|
||||
#: classes/Ostatus_profile.php:561
|
||||
#, php-format
|
||||
msgid "No content for notice %s."
|
||||
msgstr "Нема содржина за забелешката %s."
|
||||
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
|
||||
#: classes/Ostatus_profile.php:592
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
|
||||
#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
|
||||
#: classes/Ostatus_profile.php:596
|
||||
msgid "Show more"
|
||||
msgstr "Повеќе"
|
||||
|
||||
#. TRANS: Exception. %s is a profile URL.
|
||||
#: classes/Ostatus_profile.php:785
|
||||
#: classes/Ostatus_profile.php:789
|
||||
#, php-format
|
||||
msgid "Could not reach profile page %s."
|
||||
msgstr "Не можев да ја добијам профилната страница %s."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:843
|
||||
#. TRANS: Exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:847
|
||||
#, php-format
|
||||
msgid "Could not find a feed URL for profile page %s."
|
||||
msgstr "Не можев да пронајдам каналска URL-адреса за профилната страница %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:980
|
||||
#. TRANS: Feed sub exception.
|
||||
#: classes/Ostatus_profile.php:985
|
||||
msgid "Can't find enough profile information to make a feed."
|
||||
msgstr "Не можев да најдам доволно профилни податоци за да направам канал."
|
||||
|
||||
#: classes/Ostatus_profile.php:1039
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1045
|
||||
#, php-format
|
||||
msgid "Invalid avatar URL %s."
|
||||
msgstr "Неважечка URL-адреса за аватарот: %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:1049
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:1056
|
||||
#, php-format
|
||||
msgid "Tried to update avatar for unsaved remote profile %s."
|
||||
msgstr ""
|
||||
"Се обидов да го подновам аватарот за незачуваниот далечински профил %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:1058
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1066
|
||||
#, php-format
|
||||
msgid "Unable to fetch avatar from %s."
|
||||
msgstr "Не можам да го добијам аватарот од %s."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1284
|
||||
#: classes/Ostatus_profile.php:1292
|
||||
msgid "Local user can't be referenced as remote."
|
||||
msgstr "Локалниот корисник не може да се наведе како далечински."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1289
|
||||
#: classes/Ostatus_profile.php:1297
|
||||
msgid "Local group can't be referenced as remote."
|
||||
msgstr "Локалната група не може да се наведе како далечинска."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
|
||||
msgid "Can't save local profile."
|
||||
msgstr "Не можам да го зачувам локалниот профил."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1360
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1368
|
||||
msgid "Can't save OStatus profile."
|
||||
msgstr "Не можам да го зачувам профилот од OStatus."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
|
||||
#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
|
||||
msgid "Not a valid webfinger address."
|
||||
msgstr "Ова не е важечка Webfinger-адреса"
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1729
|
||||
#: classes/Ostatus_profile.php:1737
|
||||
#, php-format
|
||||
msgid "Couldn't save profile for \"%s\"."
|
||||
msgstr "Не можам да го зачувам профилот за „%s“."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1748
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#, php-format
|
||||
msgid "Couldn't save ostatus_profile for \"%s\"."
|
||||
msgstr "Не можам да го зачувам ostatus_profile за „%s“."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#: classes/Ostatus_profile.php:1764
|
||||
#, php-format
|
||||
msgid "Couldn't find a valid profile for \"%s\"."
|
||||
msgstr "Не можев да пронајдам важечки профил за „%s“."
|
||||
|
||||
#: classes/Ostatus_profile.php:1798
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1807
|
||||
msgid "Could not store HTML content of long post as file."
|
||||
msgstr ""
|
||||
"Не можам да ја складирам HTML-содржината на долгата објава како податотека."
|
||||
|
@ -10,13 +10,13 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - OStatus\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-10-27 23:43+0000\n"
|
||||
"PO-Revision-Date: 2010-10-27 23:47:16+0000\n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
|
||||
"Language-Team: Dutch <http://translatewiki.net/wiki/Portal:nl>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
|
||||
"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: nl\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
|
||||
@ -133,7 +133,7 @@ msgid "Attempting to end PuSH subscription for feed with no hub."
|
||||
msgstr ""
|
||||
"Aan het proberen een PuSH-abonnement te verwijderen voor een feed zonder hub."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:192
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
|
||||
@ -141,7 +141,7 @@ msgstr ""
|
||||
"Ongeldige ostatus_profile status: het ID voor zowel de groep als het profiel "
|
||||
"voor %s is ingesteld."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:195
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
|
||||
@ -165,112 +165,120 @@ msgstr ""
|
||||
"Ongeldig type doorgegeven aan Ostatus_profile::notify. Het moet een XML-"
|
||||
"string of Activity zijn."
|
||||
|
||||
#: classes/Ostatus_profile.php:408
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:409
|
||||
msgid "Unknown feed format."
|
||||
msgstr "Onbekend feedformaat"
|
||||
|
||||
#: classes/Ostatus_profile.php:431
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:433
|
||||
msgid "RSS feed without a channel."
|
||||
msgstr "RSS-feed zonder kanaal."
|
||||
|
||||
#. TRANS: Client exception.
|
||||
#: classes/Ostatus_profile.php:476
|
||||
#: classes/Ostatus_profile.php:478
|
||||
msgid "Can't handle that kind of post."
|
||||
msgstr "Dat type post kan niet verwerkt worden."
|
||||
|
||||
#. TRANS: Client exception. %s is a source URL.
|
||||
#: classes/Ostatus_profile.php:559
|
||||
#. TRANS: Client exception. %s is a source URI.
|
||||
#: classes/Ostatus_profile.php:561
|
||||
#, php-format
|
||||
msgid "No content for notice %s."
|
||||
msgstr "Geen inhoud voor mededeling %s."
|
||||
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
|
||||
#: classes/Ostatus_profile.php:592
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
|
||||
#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
|
||||
#: classes/Ostatus_profile.php:596
|
||||
msgid "Show more"
|
||||
msgstr "Meer weergeven"
|
||||
|
||||
#. TRANS: Exception. %s is a profile URL.
|
||||
#: classes/Ostatus_profile.php:785
|
||||
#: classes/Ostatus_profile.php:789
|
||||
#, php-format
|
||||
msgid "Could not reach profile page %s."
|
||||
msgstr "Het was niet mogelijk de profielpagina %s te bereiken."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:843
|
||||
#. TRANS: Exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:847
|
||||
#, php-format
|
||||
msgid "Could not find a feed URL for profile page %s."
|
||||
msgstr "Het was niet mogelijk de feed-URL voor de profielpagina %s te vinden."
|
||||
|
||||
#: classes/Ostatus_profile.php:980
|
||||
#. TRANS: Feed sub exception.
|
||||
#: classes/Ostatus_profile.php:985
|
||||
msgid "Can't find enough profile information to make a feed."
|
||||
msgstr ""
|
||||
"Het was niet mogelijk voldoende profielinformatie te vinden om een feed te "
|
||||
"maken."
|
||||
|
||||
#: classes/Ostatus_profile.php:1039
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1045
|
||||
#, php-format
|
||||
msgid "Invalid avatar URL %s."
|
||||
msgstr "Ongeldige avatar-URL %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:1049
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:1056
|
||||
#, php-format
|
||||
msgid "Tried to update avatar for unsaved remote profile %s."
|
||||
msgstr ""
|
||||
"Geprobeerd om een avatar bij te werken voor het niet opgeslagen profiel %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:1058
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1066
|
||||
#, php-format
|
||||
msgid "Unable to fetch avatar from %s."
|
||||
msgstr "Het was niet mogelijk de avatar op te halen van %s."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1284
|
||||
#: classes/Ostatus_profile.php:1292
|
||||
msgid "Local user can't be referenced as remote."
|
||||
msgstr ""
|
||||
"Naar een lokale gebruiker kan niet verwezen worden alsof die zich bij een "
|
||||
"andere dienst bevindt."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1289
|
||||
#: classes/Ostatus_profile.php:1297
|
||||
msgid "Local group can't be referenced as remote."
|
||||
msgstr ""
|
||||
"Naar een lokale groep kan niet verwezen worden alsof die zich bij een andere "
|
||||
"dienst bevindt."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
|
||||
msgid "Can't save local profile."
|
||||
msgstr "Het was niet mogelijk het lokale profiel op te slaan."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1360
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1368
|
||||
msgid "Can't save OStatus profile."
|
||||
msgstr "Het was niet mogelijk het Ostatusprofiel op te slaan."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
|
||||
#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
|
||||
msgid "Not a valid webfinger address."
|
||||
msgstr "Geen geldig webfingeradres."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1729
|
||||
#: classes/Ostatus_profile.php:1737
|
||||
#, php-format
|
||||
msgid "Couldn't save profile for \"%s\"."
|
||||
msgstr "Het was niet mogelijk het profiel voor \"%s\" op te slaan."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1748
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#, php-format
|
||||
msgid "Couldn't save ostatus_profile for \"%s\"."
|
||||
msgstr "Het was niet mogelijk het ostatus_profile voor \"%s\" op te slaan."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#: classes/Ostatus_profile.php:1764
|
||||
#, php-format
|
||||
msgid "Couldn't find a valid profile for \"%s\"."
|
||||
msgstr "Er is geen geldig profiel voor \"%s\" gevonden."
|
||||
|
||||
#: classes/Ostatus_profile.php:1798
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1807
|
||||
msgid "Could not store HTML content of long post as file."
|
||||
msgstr ""
|
||||
"Het was niet mogelijk de HTML-inhoud van het lange bericht als bestand op te "
|
||||
|
@ -9,13 +9,13 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - OStatus\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-10-27 23:43+0000\n"
|
||||
"PO-Revision-Date: 2010-10-27 23:47:17+0000\n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:54:51+0000\n"
|
||||
"Language-Team: Ukrainian <http://translatewiki.net/wiki/Portal:uk>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-10-23 19:00:35+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75596); Translate extension (2010-09-17)\n"
|
||||
"X-POT-Import-Date: 2010-10-29 16:13:55+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: uk\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-ostatus\n"
|
||||
@ -130,7 +130,7 @@ msgstr ""
|
||||
"Спроба скасувати підписку за допомогою PuSH до веб-стрічки, котра не має "
|
||||
"вузла."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:192
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs set for %s."
|
||||
@ -138,7 +138,7 @@ msgstr ""
|
||||
"Невірний стан параметру ostatus_profile: як групові, так і персональні "
|
||||
"ідентифікатори встановлено для %s."
|
||||
|
||||
#. TRANS: Server exception.
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:195
|
||||
#, php-format
|
||||
msgid "Invalid ostatus_profile state: both group and profile IDs empty for %s."
|
||||
@ -162,106 +162,114 @@ msgstr ""
|
||||
"До параметру Ostatus_profile::notify передано невірний тип. Це має бути або "
|
||||
"рядок у форматі XML, або запис активності."
|
||||
|
||||
#: classes/Ostatus_profile.php:408
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:409
|
||||
msgid "Unknown feed format."
|
||||
msgstr "Невідомий формат веб-стрічки."
|
||||
|
||||
#: classes/Ostatus_profile.php:431
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:433
|
||||
msgid "RSS feed without a channel."
|
||||
msgstr "RSS-стрічка не має каналу."
|
||||
|
||||
#. TRANS: Client exception.
|
||||
#: classes/Ostatus_profile.php:476
|
||||
#: classes/Ostatus_profile.php:478
|
||||
msgid "Can't handle that kind of post."
|
||||
msgstr "Не вдається обробити такий тип допису."
|
||||
|
||||
#. TRANS: Client exception. %s is a source URL.
|
||||
#: classes/Ostatus_profile.php:559
|
||||
#. TRANS: Client exception. %s is a source URI.
|
||||
#: classes/Ostatus_profile.php:561
|
||||
#, php-format
|
||||
msgid "No content for notice %s."
|
||||
msgstr "Допис %s не має змісту."
|
||||
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present.
|
||||
#: classes/Ostatus_profile.php:592
|
||||
#. TRANS: Shown when a notice is longer than supported and/or when attachments are present. At runtime
|
||||
#. TRANS: this will usually be replaced with localised text from StatusNet core messages.
|
||||
#: classes/Ostatus_profile.php:596
|
||||
msgid "Show more"
|
||||
msgstr "Розгорнути"
|
||||
|
||||
#. TRANS: Exception. %s is a profile URL.
|
||||
#: classes/Ostatus_profile.php:785
|
||||
#: classes/Ostatus_profile.php:789
|
||||
#, php-format
|
||||
msgid "Could not reach profile page %s."
|
||||
msgstr "Не вдалося досягти сторінки профілю %s."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:843
|
||||
#. TRANS: Exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:847
|
||||
#, php-format
|
||||
msgid "Could not find a feed URL for profile page %s."
|
||||
msgstr "Не вдалося знайти URL веб-стрічки для сторінки профілю %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:980
|
||||
#. TRANS: Feed sub exception.
|
||||
#: classes/Ostatus_profile.php:985
|
||||
msgid "Can't find enough profile information to make a feed."
|
||||
msgstr ""
|
||||
"Не можу знайти достатньо інформації про профіль, аби сформувати веб-стрічку."
|
||||
|
||||
#: classes/Ostatus_profile.php:1039
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1045
|
||||
#, php-format
|
||||
msgid "Invalid avatar URL %s."
|
||||
msgstr "Невірна URL-адреса аватари %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:1049
|
||||
#. TRANS: Server exception. %s is a URI.
|
||||
#: classes/Ostatus_profile.php:1056
|
||||
#, php-format
|
||||
msgid "Tried to update avatar for unsaved remote profile %s."
|
||||
msgstr "Намагаюся оновити аватару для не збереженого віддаленого профілю %s."
|
||||
|
||||
#: classes/Ostatus_profile.php:1058
|
||||
#. TRANS: Server exception. %s is a URL.
|
||||
#: classes/Ostatus_profile.php:1066
|
||||
#, php-format
|
||||
msgid "Unable to fetch avatar from %s."
|
||||
msgstr "Неможливо завантажити аватару з %s."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1284
|
||||
#: classes/Ostatus_profile.php:1292
|
||||
msgid "Local user can't be referenced as remote."
|
||||
msgstr "Місцевий користувач не може бути зазначеним у якості віддаленого."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1289
|
||||
#: classes/Ostatus_profile.php:1297
|
||||
msgid "Local group can't be referenced as remote."
|
||||
msgstr "Локальну спільноту не можна зазначити у якості віддаленої."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1341 classes/Ostatus_profile.php:1352
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1349 classes/Ostatus_profile.php:1360
|
||||
msgid "Can't save local profile."
|
||||
msgstr "Не вдається зберегти місцевий профіль."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1360
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1368
|
||||
msgid "Can't save OStatus profile."
|
||||
msgstr "Не вдається зберегти профіль OStatus."
|
||||
|
||||
#. TRANS: Exception.
|
||||
#: classes/Ostatus_profile.php:1619 classes/Ostatus_profile.php:1647
|
||||
#: classes/Ostatus_profile.php:1627 classes/Ostatus_profile.php:1655
|
||||
msgid "Not a valid webfinger address."
|
||||
msgstr "Це недійсна адреса для протоколу WebFinger."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1729
|
||||
#: classes/Ostatus_profile.php:1737
|
||||
#, php-format
|
||||
msgid "Couldn't save profile for \"%s\"."
|
||||
msgstr "Не можу зберегти профіль для «%s»."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1748
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#, php-format
|
||||
msgid "Couldn't save ostatus_profile for \"%s\"."
|
||||
msgstr "Не можу зберегти профіль OStatus для «%s»."
|
||||
|
||||
#. TRANS: Exception. %s is a webfinger address.
|
||||
#: classes/Ostatus_profile.php:1756
|
||||
#: classes/Ostatus_profile.php:1764
|
||||
#, php-format
|
||||
msgid "Couldn't find a valid profile for \"%s\"."
|
||||
msgstr "не можу знайти відповідний й профіль для «%s»."
|
||||
|
||||
#: classes/Ostatus_profile.php:1798
|
||||
#. TRANS: Server exception.
|
||||
#: classes/Ostatus_profile.php:1807
|
||||
msgid "Could not store HTML content of long post as file."
|
||||
msgstr "Не можу зберегти HTML місткого допису у якості файлу."
|
||||
|
||||
|
58
plugins/Realtime/locale/ia/LC_MESSAGES/Realtime.po
Normal file
58
plugins/Realtime/locale/ia/LC_MESSAGES/Realtime.po
Normal file
@ -0,0 +1,58 @@
|
||||
# Translation of StatusNet - Realtime to Interlingua (Interlingua)
|
||||
# Expored from translatewiki.net
|
||||
#
|
||||
# Author: McDutchie
|
||||
# --
|
||||
# This file is distributed under the same license as the StatusNet package.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - Realtime\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:54:57+0000\n"
|
||||
"Language-Team: Interlingua <http://translatewiki.net/wiki/Portal:ia>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-11-02 19:54:39+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: ia\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-realtime\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. TRANS: Text label for realtime view "play" button, usually replaced by an icon.
|
||||
#: RealtimePlugin.php:339
|
||||
msgctxt "BUTTON"
|
||||
msgid "Play"
|
||||
msgstr "Reproducer"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "play" button.
|
||||
#: RealtimePlugin.php:341
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Play"
|
||||
msgstr "Reproducer"
|
||||
|
||||
#. TRANS: Text label for realtime view "pause" button
|
||||
#: RealtimePlugin.php:343
|
||||
msgctxt "BUTTON"
|
||||
msgid "Pause"
|
||||
msgstr "Pausar"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "pause" button
|
||||
#: RealtimePlugin.php:345
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Pause"
|
||||
msgstr "Pausar"
|
||||
|
||||
#. TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
|
||||
#: RealtimePlugin.php:347
|
||||
msgctxt "BUTTON"
|
||||
msgid "Pop up"
|
||||
msgstr "Fenestra"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "popup" button.
|
||||
#: RealtimePlugin.php:349
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Pop up in a window"
|
||||
msgstr "Aperir le reproductor in un nove fenestra"
|
58
plugins/Realtime/locale/mk/LC_MESSAGES/Realtime.po
Normal file
58
plugins/Realtime/locale/mk/LC_MESSAGES/Realtime.po
Normal file
@ -0,0 +1,58 @@
|
||||
# Translation of StatusNet - Realtime to Macedonian (Македонски)
|
||||
# Expored from translatewiki.net
|
||||
#
|
||||
# Author: Bjankuloski06
|
||||
# --
|
||||
# This file is distributed under the same license as the StatusNet package.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - Realtime\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:54:57+0000\n"
|
||||
"Language-Team: Macedonian <http://translatewiki.net/wiki/Portal:mk>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-11-02 19:54:39+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: mk\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-realtime\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n == 1 || n%10 == 1) ? 0 : 1;\n"
|
||||
|
||||
#. TRANS: Text label for realtime view "play" button, usually replaced by an icon.
|
||||
#: RealtimePlugin.php:339
|
||||
msgctxt "BUTTON"
|
||||
msgid "Play"
|
||||
msgstr "Пушти"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "play" button.
|
||||
#: RealtimePlugin.php:341
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Play"
|
||||
msgstr "Пушти"
|
||||
|
||||
#. TRANS: Text label for realtime view "pause" button
|
||||
#: RealtimePlugin.php:343
|
||||
msgctxt "BUTTON"
|
||||
msgid "Pause"
|
||||
msgstr "Паузирај"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "pause" button
|
||||
#: RealtimePlugin.php:345
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Pause"
|
||||
msgstr "Паузирај"
|
||||
|
||||
#. TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
|
||||
#: RealtimePlugin.php:347
|
||||
msgctxt "BUTTON"
|
||||
msgid "Pop up"
|
||||
msgstr "Прозорче"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "popup" button.
|
||||
#: RealtimePlugin.php:349
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Pop up in a window"
|
||||
msgstr "Прикажи во прозорче"
|
58
plugins/Realtime/locale/nl/LC_MESSAGES/Realtime.po
Normal file
58
plugins/Realtime/locale/nl/LC_MESSAGES/Realtime.po
Normal file
@ -0,0 +1,58 @@
|
||||
# Translation of StatusNet - Realtime to Dutch (Nederlands)
|
||||
# Expored from translatewiki.net
|
||||
#
|
||||
# Author: Siebrand
|
||||
# --
|
||||
# This file is distributed under the same license as the StatusNet package.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - Realtime\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:54:57+0000\n"
|
||||
"Language-Team: Dutch <http://translatewiki.net/wiki/Portal:nl>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-11-02 19:54:39+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: nl\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-realtime\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#. TRANS: Text label for realtime view "play" button, usually replaced by an icon.
|
||||
#: RealtimePlugin.php:339
|
||||
msgctxt "BUTTON"
|
||||
msgid "Play"
|
||||
msgstr "Afspelen"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "play" button.
|
||||
#: RealtimePlugin.php:341
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Play"
|
||||
msgstr "Afspelen"
|
||||
|
||||
#. TRANS: Text label for realtime view "pause" button
|
||||
#: RealtimePlugin.php:343
|
||||
msgctxt "BUTTON"
|
||||
msgid "Pause"
|
||||
msgstr "Pauzeren"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "pause" button
|
||||
#: RealtimePlugin.php:345
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Pause"
|
||||
msgstr "Pauzeren"
|
||||
|
||||
#. TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
|
||||
#: RealtimePlugin.php:347
|
||||
msgctxt "BUTTON"
|
||||
msgid "Pop up"
|
||||
msgstr "Pop-up"
|
||||
|
||||
#. TRANS: Tooltip for realtime view "popup" button.
|
||||
#: RealtimePlugin.php:349
|
||||
msgctxt "TOOLTIP"
|
||||
msgid "Pop up in a window"
|
||||
msgstr "In nieuw venstertje weergeven"
|
@ -200,8 +200,15 @@ class TwitterBridgePlugin extends Plugin
|
||||
return false;
|
||||
case 'TwitterOAuthClient':
|
||||
case 'TwitterQueueHandler':
|
||||
case 'TwitterImport':
|
||||
case 'JsonStreamReader':
|
||||
case 'TwitterStreamReader':
|
||||
include_once $dir . '/' . strtolower($cls) . '.php';
|
||||
return false;
|
||||
case 'TwitterSiteStream':
|
||||
case 'TwitterUserStream':
|
||||
include_once $dir . '/twitterstreamreader.php';
|
||||
return false;
|
||||
case 'Notice_to_status':
|
||||
case 'Twitter_synch_status':
|
||||
include_once $dir . '/' . $cls . '.php';
|
||||
@ -267,7 +274,11 @@ class TwitterBridgePlugin extends Plugin
|
||||
function onEndInitializeQueueManager($manager)
|
||||
{
|
||||
if (self::hasKeys()) {
|
||||
// Outgoing notices -> twitter
|
||||
$manager->connect('twitter', 'TwitterQueueHandler');
|
||||
|
||||
// Incoming statuses <- twitter
|
||||
$manager->connect('tweetin', 'TweetInQueueHandler');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
314
plugins/TwitterBridge/daemons/twitterdaemon.php
Normal file
314
plugins/TwitterBridge/daemons/twitterdaemon.php
Normal file
@ -0,0 +1,314 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/*
|
||||
* StatusNet - the distributed open-source microblogging tool
|
||||
* Copyright (C) 2008-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/>.
|
||||
*/
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
|
||||
|
||||
$shortoptions = 'fi::a';
|
||||
$longoptions = array('id::', 'foreground', 'all');
|
||||
|
||||
$helptext = <<<END_OF_XMPP_HELP
|
||||
Daemon script for receiving new notices from Twitter users.
|
||||
|
||||
-i --id Identity (default none)
|
||||
-a --all Handle Twitter for all local sites
|
||||
(requires Stomp queue handler, status_network setup)
|
||||
-f --foreground Stay in the foreground (default background)
|
||||
|
||||
END_OF_XMPP_HELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
|
||||
require_once INSTALLDIR . '/lib/jabber.php';
|
||||
|
||||
class TwitterDaemon extends SpawningDaemon
|
||||
{
|
||||
protected $allsites = false;
|
||||
|
||||
function __construct($id=null, $daemonize=true, $threads=1, $allsites=false)
|
||||
{
|
||||
if ($threads != 1) {
|
||||
// This should never happen. :)
|
||||
throw new Exception("TwitterDaemon must run single-threaded");
|
||||
}
|
||||
parent::__construct($id, $daemonize, $threads);
|
||||
$this->allsites = $allsites;
|
||||
}
|
||||
|
||||
function runThread()
|
||||
{
|
||||
common_log(LOG_INFO, 'Waiting to listen to Twitter and queues');
|
||||
|
||||
$master = new TwitterMaster($this->get_id(), $this->processManager());
|
||||
$master->init($this->allsites);
|
||||
$master->service();
|
||||
|
||||
common_log(LOG_INFO, 'terminating normally');
|
||||
|
||||
return $master->respawn ? self::EXIT_RESTART : self::EXIT_SHUTDOWN;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TwitterMaster extends IoMaster
|
||||
{
|
||||
protected $processManager;
|
||||
|
||||
function __construct($id, $processManager)
|
||||
{
|
||||
parent::__construct($id);
|
||||
$this->processManager = $processManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize IoManagers for the currently configured site
|
||||
* which are appropriate to this instance.
|
||||
*/
|
||||
function initManagers()
|
||||
{
|
||||
$qm = QueueManager::get();
|
||||
$qm->setActiveGroup('twitter');
|
||||
$this->instantiate($qm);
|
||||
$this->instantiate(new TwitterManager());
|
||||
$this->instantiate($this->processManager);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TwitterManager extends IoManager
|
||||
{
|
||||
// Recommended resource limits from http://dev.twitter.com/pages/site_streams
|
||||
const MAX_STREAMS = 1000;
|
||||
const USERS_PER_STREAM = 100;
|
||||
const STREAMS_PER_SECOND = 20;
|
||||
|
||||
protected $streams;
|
||||
protected $users;
|
||||
|
||||
/**
|
||||
* Pull the site's active Twitter-importing users and start spawning
|
||||
* some data streams for them!
|
||||
*
|
||||
* @fixme check their last-id and check whether we'll need to do a manual pull.
|
||||
* @fixme abstract out the fetching so we can work over multiple sites.
|
||||
*/
|
||||
protected function initStreams()
|
||||
{
|
||||
common_log(LOG_INFO, 'init...');
|
||||
// Pull Twitter user IDs for all users we want to pull data for
|
||||
$flink = new Foreign_link();
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
// @fixme probably should do the bitfield check in a whereAdd but it's ugly :D
|
||||
$flink->find();
|
||||
|
||||
$userIds = array();
|
||||
while ($flink->fetch()) {
|
||||
if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
|
||||
FOREIGN_NOTICE_RECV) {
|
||||
$userIds[] = $flink->foreign_id;
|
||||
|
||||
if (count($userIds) >= self::USERS_PER_STREAM) {
|
||||
$this->spawnStream($userIds);
|
||||
$userIds = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($userIds)) {
|
||||
$this->spawnStream($userIds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a Site Stream connection for the given chunk of users.
|
||||
* The actual connection will be opened later.
|
||||
*
|
||||
* @param $userIds array of Twitter-side user IDs
|
||||
*/
|
||||
protected function spawnStream($userIds)
|
||||
{
|
||||
$stream = $this->initSiteStream();
|
||||
$stream->followUsers($userIds);
|
||||
|
||||
// Slip the stream reader into our list of active streams.
|
||||
// We'll manage its actual connection on the next go-around.
|
||||
$this->streams[] = $stream;
|
||||
|
||||
// Record the user->stream mappings; this makes it easier for us to know
|
||||
// later if we need to kill something.
|
||||
foreach ($userIds as $id) {
|
||||
$this->users[$id] = $stream;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a generic site streams connection object.
|
||||
* All our connections will look like this, then we'll add users to them.
|
||||
*
|
||||
* @return TwitterStreamReader
|
||||
*/
|
||||
protected function initSiteStream()
|
||||
{
|
||||
$auth = $this->siteStreamAuth();
|
||||
$stream = new TwitterSiteStream($auth);
|
||||
|
||||
// Add our event handler callbacks. Whee!
|
||||
$this->setupEvents($stream);
|
||||
return $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the Twitter OAuth credentials to use to connect to the Site Streams API.
|
||||
*
|
||||
* This will use the locally-stored credentials for the applictation's owner account
|
||||
* from the site configuration. These should be configured through the administration
|
||||
* panels or manually in the config file.
|
||||
*
|
||||
* Will throw an exception if no credentials can be found -- but beware that invalid
|
||||
* credentials won't cause breakage until later.
|
||||
*
|
||||
* @return TwitterOAuthClient
|
||||
*/
|
||||
protected function siteStreamAuth()
|
||||
{
|
||||
$token = common_config('twitter', 'stream_token');
|
||||
$secret = common_config('twitter', 'stream_secret');
|
||||
if (empty($token) || empty($secret)) {
|
||||
throw new ServerException('Twitter site streams have not been correctly configured. Configure the app owner account via the admin panel.');
|
||||
}
|
||||
return new TwitterOAuthClient($token, $secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the sockets for all active connections for i/o monitoring.
|
||||
*
|
||||
* @return array of resources
|
||||
*/
|
||||
public function getSockets()
|
||||
{
|
||||
$sockets = array();
|
||||
foreach ($this->streams as $stream) {
|
||||
foreach ($stream->getSockets() as $socket) {
|
||||
$sockets[] = $socket;
|
||||
}
|
||||
}
|
||||
return $sockets;
|
||||
}
|
||||
|
||||
/**
|
||||
* We're ready to process input from one of our data sources! Woooooo!
|
||||
* @fixme is there an easier way to map from socket back to owning module? :(
|
||||
*
|
||||
* @param resource $socket
|
||||
* @return boolean success
|
||||
*/
|
||||
public function handleInput($socket)
|
||||
{
|
||||
foreach ($this->streams as $stream) {
|
||||
foreach ($stream->getSockets() as $aSocket) {
|
||||
if ($socket === $aSocket) {
|
||||
$stream->handleInput($socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the i/o system up! Prepare our connections and start opening them.
|
||||
*
|
||||
* @fixme do some rate-limiting on the stream setup
|
||||
* @fixme do some sensible backoff on failure etc
|
||||
*/
|
||||
public function start()
|
||||
{
|
||||
$this->initStreams();
|
||||
foreach ($this->streams as $stream) {
|
||||
$stream->connect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close down our connections when the daemon wraps up for business.
|
||||
*/
|
||||
public function finish()
|
||||
{
|
||||
foreach ($this->streams as $index => $stream) {
|
||||
$stream->close();
|
||||
unset($this->streams[$index]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function get()
|
||||
{
|
||||
throw new Exception('not a singleton');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up event handlers on the streaming interface.
|
||||
*
|
||||
* @fixme add more event types as we add handling for them
|
||||
*/
|
||||
protected function setupEvents(TwitterStreamReader $stream)
|
||||
{
|
||||
$handlers = array(
|
||||
'status',
|
||||
);
|
||||
foreach ($handlers as $event) {
|
||||
$stream->hookEvent($event, array($this, 'onTwitter' . ucfirst($event)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event callback notifying that a user has a new message in their home timeline.
|
||||
* We store the incoming message into the queues for processing, keeping our own
|
||||
* daemon running as shiny-fast as possible.
|
||||
*
|
||||
* @param object $status JSON data: Twitter status update
|
||||
* @fixme in all-sites mode we may need to route queue items into another site's
|
||||
* destination queues, or multiple sites.
|
||||
*/
|
||||
protected function onTwitterStatus($status, $context)
|
||||
{
|
||||
$data = array(
|
||||
'status' => $status,
|
||||
'for_user' => $context->for_user,
|
||||
);
|
||||
$qm = QueueManager::get();
|
||||
$qm->enqueue($data, 'tweetin');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (have_option('i', 'id')) {
|
||||
$id = get_option_value('i', 'id');
|
||||
} else if (count($args) > 0) {
|
||||
$id = $args[0];
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
|
||||
$foreground = have_option('f', 'foreground');
|
||||
$all = have_option('a') || have_option('--all');
|
||||
|
||||
$daemon = new TwitterDaemon($id, !$foreground, 1, $all);
|
||||
|
||||
$daemon->runOnce();
|
@ -192,25 +192,12 @@ class TwitterStatusFetcher extends ParallelizingDaemon
|
||||
|
||||
common_debug(LOG_INFO, $this->name() . ' - Retrieved ' . sizeof($timeline) . ' statuses from Twitter.');
|
||||
|
||||
$importer = new TwitterImport();
|
||||
|
||||
// Reverse to preserve order
|
||||
|
||||
foreach (array_reverse($timeline) as $status) {
|
||||
// Hacktastic: filter out stuff coming from this StatusNet
|
||||
$source = mb_strtolower(common_config('integration', 'source'));
|
||||
|
||||
if (preg_match("/$source/", mb_strtolower($status->source))) {
|
||||
common_debug($this->name() . ' - Skipping import of status ' .
|
||||
$status->id . ' with source ' . $source);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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);
|
||||
$notice = $importer->importStatus($status);
|
||||
|
||||
if (!empty($notice)) {
|
||||
Inbox::insertNotice($flink->user_id, $notice->id);
|
||||
@ -226,578 +213,6 @@ class TwitterStatusFetcher extends ParallelizingDaemon
|
||||
$flink->last_noticesync = common_sql_now();
|
||||
$flink->update();
|
||||
}
|
||||
|
||||
function saveStatus($status)
|
||||
{
|
||||
$profile = $this->ensureProfile($status->user);
|
||||
|
||||
if (empty($profile)) {
|
||||
common_log(LOG_ERR, $this->name() .
|
||||
' - Problem saving notice. No associated Profile.');
|
||||
return null;
|
||||
}
|
||||
|
||||
$statusUri = $this->makeStatusURI($status->user->screen_name, $status->id);
|
||||
|
||||
// check to see if we've already imported the status
|
||||
$n2s = Notice_to_status::staticGet('status_id', $status->id);
|
||||
|
||||
if (!empty($n2s)) {
|
||||
common_log(
|
||||
LOG_INFO,
|
||||
$this->name() .
|
||||
" - Ignoring duplicate import: {$status->id}"
|
||||
);
|
||||
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(_m('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();
|
||||
|
||||
$notice->profile_id = $profile->id;
|
||||
$notice->uri = $statusUri;
|
||||
$notice->url = $statusUri;
|
||||
$notice->created = strftime(
|
||||
'%Y-%m-%d %H:%M:%S',
|
||||
strtotime($status->created_at)
|
||||
);
|
||||
|
||||
$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 = html_entity_decode($status->text, ENT_QUOTES, 'UTF-8');
|
||||
$notice->rendered = $this->linkify($status);
|
||||
|
||||
if (Event::handle('StartNoticeSave', array(&$notice))) {
|
||||
|
||||
$id = $notice->insert();
|
||||
|
||||
if (!$id) {
|
||||
common_log_db_error($notice, 'INSERT', __FILE__);
|
||||
common_log(LOG_ERR, $this->name() .
|
||||
' - Problem saving notice.');
|
||||
}
|
||||
|
||||
Event::handle('EndNoticeSave', array($notice));
|
||||
}
|
||||
|
||||
Notice_to_status::saveNew($notice->id, $status->id);
|
||||
|
||||
$this->saveStatusMentions($notice, $status);
|
||||
|
||||
$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.
|
||||
*
|
||||
* @param string $nickname local nickname of the Twitter user
|
||||
* @param string $profileurl the profile url
|
||||
*
|
||||
* @return mixed value the first Profile with that url, or null
|
||||
*/
|
||||
function getProfileByUrl($nickname, $profileurl)
|
||||
{
|
||||
$profile = new Profile();
|
||||
$profile->nickname = $nickname;
|
||||
$profile->profileurl = $profileurl;
|
||||
$profile->limit(1);
|
||||
|
||||
if ($profile->find()) {
|
||||
$profile->fetch();
|
||||
return $profile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this Twitter status has already been imported
|
||||
*
|
||||
* @param Profile $profile Twitter user's local profile
|
||||
* @param string $statusUri URI of the status on Twitter
|
||||
*
|
||||
* @return mixed value a matching Notice or null
|
||||
*/
|
||||
function checkDupe($profile, $statusUri)
|
||||
{
|
||||
$notice = new Notice();
|
||||
$notice->uri = $statusUri;
|
||||
$notice->profile_id = $profile->id;
|
||||
$notice->limit(1);
|
||||
|
||||
if ($notice->find()) {
|
||||
$notice->fetch();
|
||||
return $notice;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function ensureProfile($user)
|
||||
{
|
||||
// check to see if there's already a profile for this user
|
||||
$profileurl = 'http://twitter.com/' . $user->screen_name;
|
||||
$profile = $this->getProfileByUrl($user->screen_name, $profileurl);
|
||||
|
||||
if (!empty($profile)) {
|
||||
common_debug($this->name() .
|
||||
" - Profile for $profile->nickname found.");
|
||||
|
||||
// Check to see if the user's Avatar has changed
|
||||
|
||||
$this->checkAvatar($user, $profile);
|
||||
return $profile;
|
||||
|
||||
} else {
|
||||
common_debug($this->name() . ' - Adding profile and remote profile ' .
|
||||
"for Twitter user: $profileurl.");
|
||||
|
||||
$profile = new Profile();
|
||||
$profile->query("BEGIN");
|
||||
|
||||
$profile->nickname = $user->screen_name;
|
||||
$profile->fullname = $user->name;
|
||||
$profile->homepage = $user->url;
|
||||
$profile->bio = $user->description;
|
||||
$profile->location = $user->location;
|
||||
$profile->profileurl = $profileurl;
|
||||
$profile->created = common_sql_now();
|
||||
|
||||
try {
|
||||
$id = $profile->insert();
|
||||
} catch(Exception $e) {
|
||||
common_log(LOG_WARNING, $this->name . ' Couldn\'t insert profile - ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
common_log_db_error($profile, 'INSERT', __FILE__);
|
||||
$profile->query("ROLLBACK");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for remote profile
|
||||
|
||||
$remote_pro = Remote_profile::staticGet('uri', $profileurl);
|
||||
|
||||
if (empty($remote_pro)) {
|
||||
$remote_pro = new Remote_profile();
|
||||
|
||||
$remote_pro->id = $id;
|
||||
$remote_pro->uri = $profileurl;
|
||||
$remote_pro->created = common_sql_now();
|
||||
|
||||
try {
|
||||
$rid = $remote_pro->insert();
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING, $this->name() . ' Couldn\'t save remote profile - ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($rid)) {
|
||||
common_log_db_error($profile, 'INSERT', __FILE__);
|
||||
$profile->query("ROLLBACK");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$profile->query("COMMIT");
|
||||
|
||||
$this->saveAvatars($user, $id);
|
||||
|
||||
return $profile;
|
||||
}
|
||||
}
|
||||
|
||||
function checkAvatar($twitter_user, $profile)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$path_parts = pathinfo($twitter_user->profile_image_url);
|
||||
|
||||
$newname = 'Twitter_' . $twitter_user->id . '_' .
|
||||
$path_parts['basename'];
|
||||
|
||||
$oldname = $profile->getAvatar(48)->filename;
|
||||
|
||||
if ($newname != $oldname) {
|
||||
common_debug($this->name() . ' - Avatar for Twitter user ' .
|
||||
"$profile->nickname has changed.");
|
||||
common_debug($this->name() . " - old: $oldname new: $newname");
|
||||
|
||||
$this->updateAvatars($twitter_user, $profile);
|
||||
}
|
||||
|
||||
if ($this->missingAvatarFile($profile)) {
|
||||
common_debug($this->name() . ' - Twitter user ' .
|
||||
$profile->nickname .
|
||||
' is missing one or more local avatars.');
|
||||
common_debug($this->name() ." - old: $oldname new: $newname");
|
||||
|
||||
$this->updateAvatars($twitter_user, $profile);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAvatars($twitter_user, $profile) {
|
||||
|
||||
global $config;
|
||||
|
||||
$path_parts = pathinfo($twitter_user->profile_image_url);
|
||||
|
||||
$img_root = substr($path_parts['basename'], 0, -11);
|
||||
$ext = $path_parts['extension'];
|
||||
$mediatype = $this->getMediatype($ext);
|
||||
|
||||
foreach (array('mini', 'normal', 'bigger') as $size) {
|
||||
$url = $path_parts['dirname'] . '/' .
|
||||
$img_root . '_' . $size . ".$ext";
|
||||
$filename = 'Twitter_' . $twitter_user->id . '_' .
|
||||
$img_root . "_$size.$ext";
|
||||
|
||||
$this->updateAvatar($profile->id, $size, $mediatype, $filename);
|
||||
$this->fetchAvatar($url, $filename);
|
||||
}
|
||||
}
|
||||
|
||||
function missingAvatarFile($profile) {
|
||||
foreach (array(24, 48, 73) as $size) {
|
||||
$filename = $profile->getAvatar($size)->filename;
|
||||
$avatarpath = Avatar::path($filename);
|
||||
if (file_exists($avatarpath) == FALSE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getMediatype($ext)
|
||||
{
|
||||
$mediatype = null;
|
||||
|
||||
switch (strtolower($ext)) {
|
||||
case 'jpg':
|
||||
$mediatype = 'image/jpg';
|
||||
break;
|
||||
case 'gif':
|
||||
$mediatype = 'image/gif';
|
||||
break;
|
||||
default:
|
||||
$mediatype = 'image/png';
|
||||
}
|
||||
|
||||
return $mediatype;
|
||||
}
|
||||
|
||||
function saveAvatars($user, $id)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$path_parts = pathinfo($user->profile_image_url);
|
||||
$ext = $path_parts['extension'];
|
||||
$end = strlen('_normal' . $ext);
|
||||
$img_root = substr($path_parts['basename'], 0, -($end+1));
|
||||
$mediatype = $this->getMediatype($ext);
|
||||
|
||||
foreach (array('mini', 'normal', 'bigger') as $size) {
|
||||
$url = $path_parts['dirname'] . '/' .
|
||||
$img_root . '_' . $size . ".$ext";
|
||||
$filename = 'Twitter_' . $user->id . '_' .
|
||||
$img_root . "_$size.$ext";
|
||||
|
||||
if ($this->fetchAvatar($url, $filename)) {
|
||||
$this->newAvatar($id, $size, $mediatype, $filename);
|
||||
} else {
|
||||
common_log(LOG_WARNING, $id() .
|
||||
" - Problem fetching Avatar: $url");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateAvatar($profile_id, $size, $mediatype, $filename) {
|
||||
|
||||
common_debug($this->name() . " - Updating avatar: $size");
|
||||
|
||||
$profile = Profile::staticGet($profile_id);
|
||||
|
||||
if (empty($profile)) {
|
||||
common_debug($this->name() . " - Couldn't get profile: $profile_id!");
|
||||
return;
|
||||
}
|
||||
|
||||
$sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
|
||||
$avatar = $profile->getAvatar($sizes[$size]);
|
||||
|
||||
// Delete the avatar, if present
|
||||
if ($avatar) {
|
||||
$avatar->delete();
|
||||
}
|
||||
|
||||
$this->newAvatar($profile->id, $size, $mediatype, $filename);
|
||||
}
|
||||
|
||||
function newAvatar($profile_id, $size, $mediatype, $filename)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$avatar = new Avatar();
|
||||
$avatar->profile_id = $profile_id;
|
||||
|
||||
switch($size) {
|
||||
case 'mini':
|
||||
$avatar->width = 24;
|
||||
$avatar->height = 24;
|
||||
break;
|
||||
case 'normal':
|
||||
$avatar->width = 48;
|
||||
$avatar->height = 48;
|
||||
break;
|
||||
default:
|
||||
// Note: Twitter's big avatars are a different size than
|
||||
// StatusNet's (StatusNet's = 96)
|
||||
$avatar->width = 73;
|
||||
$avatar->height = 73;
|
||||
}
|
||||
|
||||
$avatar->original = 0; // we don't have the original
|
||||
$avatar->mediatype = $mediatype;
|
||||
$avatar->filename = $filename;
|
||||
$avatar->url = Avatar::url($filename);
|
||||
|
||||
$avatar->created = common_sql_now();
|
||||
|
||||
try {
|
||||
$id = $avatar->insert();
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING, $this->name() . ' Couldn\'t insert avatar - ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
common_log_db_error($avatar, 'INSERT', __FILE__);
|
||||
return null;
|
||||
}
|
||||
|
||||
common_debug($this->name() .
|
||||
" - Saved new $size avatar for $profile_id.");
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a remote avatar image and save to local storage.
|
||||
*
|
||||
* @param string $url avatar source URL
|
||||
* @param string $filename bare local filename for download
|
||||
* @return bool true on success, false on failure
|
||||
*/
|
||||
function fetchAvatar($url, $filename)
|
||||
{
|
||||
common_debug($this->name() . " - Fetching Twitter avatar: $url");
|
||||
|
||||
$request = HTTPClient::start();
|
||||
$response = $request->get($url);
|
||||
if ($response->isOk()) {
|
||||
$avatarfile = Avatar::path($filename);
|
||||
$ok = file_put_contents($avatarfile, $response->getBody());
|
||||
if (!$ok) {
|
||||
common_log(LOG_WARNING, $this->name() .
|
||||
" - Couldn't open file $filename");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const URL = 1;
|
||||
const HASHTAG = 2;
|
||||
const MENTION = 3;
|
||||
|
||||
function linkify($status)
|
||||
{
|
||||
$text = $status->text;
|
||||
|
||||
if (empty($status->entities)) {
|
||||
common_log(LOG_WARNING, "No entities data for {$status->id}; trying to fake up links ourselves.");
|
||||
$text = common_replace_urls_callback($text, 'common_linkify');
|
||||
$text = preg_replace('/(^|\"\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.TwitterStatusFetcher::tagLink('\\2')", $text);
|
||||
$text = preg_replace('/(^|\s+)@([a-z0-9A-Z_]{1,64})/e', "'\\1@'.TwitterStatusFetcher::atLink('\\2')", $text);
|
||||
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 "#" . self::tagLink($object->text);
|
||||
}
|
||||
|
||||
function makeMentionLink($object)
|
||||
{
|
||||
return "@".self::atLink($object->screen_name, $object->name);
|
||||
}
|
||||
|
||||
static function tagLink($tag)
|
||||
{
|
||||
return "<a href='https://twitter.com/search?q=%23{$tag}' class='hashtag'>{$tag}</a>";
|
||||
}
|
||||
|
||||
static function atLink($screenName, $fullName=null)
|
||||
{
|
||||
if (!empty($fullName)) {
|
||||
return "<a href='http://twitter.com/{$screenName}' title='{$fullName}'>{$screenName}</a>";
|
||||
} else {
|
||||
return "<a href='http://twitter.com/{$screenName}'>{$screenName}</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;
|
||||
|
265
plugins/TwitterBridge/jsonstreamreader.php
Normal file
265
plugins/TwitterBridge/jsonstreamreader.php
Normal file
@ -0,0 +1,265 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* 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 Plugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 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/
|
||||
*/
|
||||
|
||||
class OAuthData
|
||||
{
|
||||
public $consumer_key, $consumer_secret, $token, $token_secret;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
abstract class JsonStreamReader
|
||||
{
|
||||
const CRLF = "\r\n";
|
||||
|
||||
public $id;
|
||||
protected $socket = null;
|
||||
protected $state = 'init'; // 'init', 'connecting', 'waiting', 'headers', 'active'
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = get_class($this) . '.' . substr(md5(mt_rand()), 0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts asynchronous connect operation...
|
||||
*
|
||||
* @fixme Can we do the open-socket fully async to? (need write select infrastructure)
|
||||
*
|
||||
* @param string $url
|
||||
*/
|
||||
public function connect($url)
|
||||
{
|
||||
common_log(LOG_DEBUG, "$this->id opening connection to $url");
|
||||
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||
if ($scheme == 'http') {
|
||||
$rawScheme = 'tcp';
|
||||
} else if ($scheme == 'https') {
|
||||
$rawScheme = 'ssl';
|
||||
} else {
|
||||
throw new ServerException('Invalid URL scheme for HTTP stream reader');
|
||||
}
|
||||
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$port = parse_url($url, PHP_URL_PORT);
|
||||
if (!$port) {
|
||||
if ($scheme == 'https') {
|
||||
$port = 443;
|
||||
} else {
|
||||
$port = 80;
|
||||
}
|
||||
}
|
||||
|
||||
$path = parse_url($url, PHP_URL_PATH);
|
||||
$query = parse_url($url, PHP_URL_QUERY);
|
||||
if ($query) {
|
||||
$path .= '?' . $query;
|
||||
}
|
||||
|
||||
$errno = $errstr = null;
|
||||
$timeout = 5;
|
||||
//$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
|
||||
$flags = STREAM_CLIENT_CONNECT;
|
||||
// @fixme add SSL params
|
||||
$this->socket = stream_socket_client("$rawScheme://$host:$port", $errno, $errstr, $timeout, $flags);
|
||||
|
||||
$this->send($this->httpOpen($host, $path));
|
||||
|
||||
stream_set_blocking($this->socket, false);
|
||||
$this->state = 'waiting';
|
||||
}
|
||||
|
||||
/**
|
||||
* Send some fun data off to the server.
|
||||
*
|
||||
* @param string $buffer
|
||||
*/
|
||||
function send($buffer)
|
||||
{
|
||||
fwrite($this->socket, $buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read next packet of data from the socket.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function read()
|
||||
{
|
||||
$buffer = fread($this->socket, 65536);
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTTP request headers.
|
||||
*
|
||||
* @param string $host
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
protected function httpOpen($host, $path)
|
||||
{
|
||||
$lines = array(
|
||||
"GET $path HTTP/1.1",
|
||||
"Host: $host",
|
||||
"User-Agent: StatusNet/" . STATUSNET_VERSION . " (TwitterBridgePlugin)",
|
||||
"Connection: close",
|
||||
"",
|
||||
""
|
||||
);
|
||||
return implode(self::CRLF, $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the current connection, if open.
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if ($this->isConnected()) {
|
||||
common_log(LOG_DEBUG, "$this->id closing connection.");
|
||||
fclose($this->socket);
|
||||
$this->socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we currently connected?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return $this->socket !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send any sockets we're listening on to the IO manager
|
||||
* to wait for input.
|
||||
*
|
||||
* @return array of resources
|
||||
*/
|
||||
public function getSockets()
|
||||
{
|
||||
if ($this->isConnected()) {
|
||||
return array($this->socket);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a chunk of input over the horn and go go go! :D
|
||||
*
|
||||
* @param string $buffer
|
||||
*/
|
||||
public function handleInput($socket)
|
||||
{
|
||||
if ($this->socket !== $socket) {
|
||||
throw new Exception('Got input from unexpected socket!');
|
||||
}
|
||||
|
||||
try {
|
||||
$buffer = $this->read();
|
||||
$lines = explode(self::CRLF, $buffer);
|
||||
foreach ($lines as $line) {
|
||||
$this->handleLine($line);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_ERR, "$this->id aborting connection due to error: " . $e->getMessage());
|
||||
fclose($this->socket);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleLine($line)
|
||||
{
|
||||
switch ($this->state)
|
||||
{
|
||||
case 'waiting':
|
||||
$this->handleLineWaiting($line);
|
||||
break;
|
||||
case 'headers':
|
||||
$this->handleLineHeaders($line);
|
||||
break;
|
||||
case 'active':
|
||||
$this->handleLineActive($line);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid state in handleLine: ' . $this->state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param <type> $line
|
||||
*/
|
||||
protected function handleLineWaiting($line)
|
||||
{
|
||||
$bits = explode(' ', $line, 3);
|
||||
if (count($bits) != 3) {
|
||||
throw new Exception("Invalid HTTP response line: $line");
|
||||
}
|
||||
|
||||
list($http, $status, $text) = $bits;
|
||||
if (substr($http, 0, 5) != 'HTTP/') {
|
||||
throw new Exception("Invalid HTTP response line chunk '$http': $line");
|
||||
}
|
||||
if ($status != '200') {
|
||||
throw new Exception("Bad HTTP response code $status: $line");
|
||||
}
|
||||
common_log(LOG_DEBUG, "$this->id $line");
|
||||
$this->state = 'headers';
|
||||
}
|
||||
|
||||
protected function handleLineHeaders($line)
|
||||
{
|
||||
if ($line == '') {
|
||||
$this->state = 'active';
|
||||
common_log(LOG_DEBUG, "$this->id connection is active!");
|
||||
} else {
|
||||
common_log(LOG_DEBUG, "$this->id read HTTP header: $line");
|
||||
$this->responseHeaders[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleLineActive($line)
|
||||
{
|
||||
if ($line == "") {
|
||||
// Server sends empty lines as keepalive.
|
||||
return;
|
||||
}
|
||||
$data = json_decode($line);
|
||||
if ($data) {
|
||||
$this->handleJson($data);
|
||||
} else {
|
||||
common_log(LOG_ERR, "$this->id received bogus JSON data: " . var_export($line, true));
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function handleJson(stdClass $data);
|
||||
}
|
147
plugins/TwitterBridge/scripts/fakestream.php
Normal file
147
plugins/TwitterBridge/scripts/fakestream.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* 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 Plugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 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/
|
||||
*/
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
|
||||
|
||||
$shortoptions = 'n:';
|
||||
$longoptions = array('nick=','import','all');
|
||||
|
||||
$helptext = <<<ENDOFHELP
|
||||
USAGE: fakestream.php -n <username>
|
||||
|
||||
-n --nick=<username> Local user whose Twitter timeline to watch
|
||||
--import Experimental: run incoming messages through import
|
||||
--all Experimental: run multiuser; requires nick be the app owner
|
||||
|
||||
Attempts a User Stream connection to Twitter as the given user, dumping
|
||||
data as it comes.
|
||||
|
||||
ENDOFHELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
|
||||
if (have_option('n')) {
|
||||
$nickname = get_option_value('n');
|
||||
} else if (have_option('nick')) {
|
||||
$nickname = get_option_value('nickname');
|
||||
} else if (have_option('all')) {
|
||||
$nickname = null;
|
||||
} else {
|
||||
show_help($helptext);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param User $user
|
||||
* @return TwitterOAuthClient
|
||||
*/
|
||||
function twitterAuthForUser(User $user)
|
||||
{
|
||||
$flink = Foreign_link::getByUserID($user->id,
|
||||
TWITTER_SERVICE);
|
||||
if (!$flink) {
|
||||
throw new ServerException("No Twitter config for this user.");
|
||||
}
|
||||
|
||||
$token = TwitterOAuthClient::unpackToken($flink->credentials);
|
||||
if (!$token) {
|
||||
throw new ServerException("No Twitter OAuth credentials for this user.");
|
||||
}
|
||||
|
||||
return new TwitterOAuthClient($token->key, $token->secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulate the line-by-line output...
|
||||
*
|
||||
* @param Foreign_link $flink
|
||||
* @param mixed $data
|
||||
*/
|
||||
function dumpMessage($flink, $data)
|
||||
{
|
||||
$msg = prepMessage($flink, $data);
|
||||
print json_encode($msg) . "\r\n";
|
||||
}
|
||||
|
||||
function prepMessage($flink, $data)
|
||||
{
|
||||
$msg->for_user = $flink->foreign_id;
|
||||
$msg->message = $data;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
if (have_option('all')) {
|
||||
$users = array();
|
||||
|
||||
$flink = new Foreign_link();
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
$flink->find();
|
||||
|
||||
while ($flink->fetch()) {
|
||||
if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
|
||||
FOREIGN_NOTICE_RECV) {
|
||||
$users[] = $flink->user_id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$user = User::staticGet('nickname', $nickname);
|
||||
$users = array($user->id);
|
||||
}
|
||||
|
||||
$output = array();
|
||||
foreach ($users as $id) {
|
||||
$user = User::staticGet('id', $id);
|
||||
if (!$user) {
|
||||
throw new Exception("No user for id $id");
|
||||
}
|
||||
$auth = twitterAuthForUser($user);
|
||||
$flink = Foreign_link::getByUserID($user->id,
|
||||
TWITTER_SERVICE);
|
||||
|
||||
$friends->friends = $auth->friendsIds();
|
||||
dumpMessage($flink, $friends);
|
||||
|
||||
$timeline = $auth->statusesHomeTimeline();
|
||||
foreach ($timeline as $status) {
|
||||
$output[] = prepMessage($flink, $status);
|
||||
}
|
||||
}
|
||||
|
||||
usort($output, function($a, $b) {
|
||||
if ($a->message->id < $b->message->id) {
|
||||
return -1;
|
||||
} else if ($a->message->id == $b->message->id) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($output as $msg) {
|
||||
print json_encode($msg) . "\r\n";
|
||||
}
|
244
plugins/TwitterBridge/scripts/streamtest.php
Normal file
244
plugins/TwitterBridge/scripts/streamtest.php
Normal file
@ -0,0 +1,244 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* 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 Plugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 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/
|
||||
*/
|
||||
|
||||
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
|
||||
|
||||
$shortoptions = 'n:';
|
||||
$longoptions = array('nick=','import','all','apiroot=');
|
||||
|
||||
$helptext = <<<ENDOFHELP
|
||||
USAGE: streamtest.php -n <username>
|
||||
|
||||
-n --nick=<username> Local user whose Twitter timeline to watch
|
||||
--import Experimental: run incoming messages through import
|
||||
--all Experimental: run multiuser; requires nick be the app owner
|
||||
--apiroot=<url> Provide alternate streaming API root URL
|
||||
|
||||
Attempts a User Stream connection to Twitter as the given user, dumping
|
||||
data as it comes.
|
||||
|
||||
ENDOFHELP;
|
||||
|
||||
require_once INSTALLDIR.'/scripts/commandline.inc';
|
||||
require_once dirname(dirname(__FILE__)) . '/jsonstreamreader.php';
|
||||
require_once dirname(dirname(__FILE__)) . '/twitterstreamreader.php';
|
||||
|
||||
if (have_option('n')) {
|
||||
$nickname = get_option_value('n');
|
||||
} else if (have_option('nick')) {
|
||||
$nickname = get_option_value('nickname');
|
||||
} else {
|
||||
show_help($helptext);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param User $user
|
||||
* @return TwitterOAuthClient
|
||||
*/
|
||||
function twitterAuthForUser(User $user)
|
||||
{
|
||||
$flink = Foreign_link::getByUserID($user->id,
|
||||
TWITTER_SERVICE);
|
||||
if (!$flink) {
|
||||
throw new ServerException("No Twitter config for this user.");
|
||||
}
|
||||
|
||||
$token = TwitterOAuthClient::unpackToken($flink->credentials);
|
||||
if (!$token) {
|
||||
throw new ServerException("No Twitter OAuth credentials for this user.");
|
||||
}
|
||||
|
||||
return new TwitterOAuthClient($token->key, $token->secret);
|
||||
}
|
||||
|
||||
function homeStreamForUser(User $user)
|
||||
{
|
||||
$auth = twitterAuthForUser($user);
|
||||
return new TwitterUserStream($auth);
|
||||
}
|
||||
|
||||
function siteStreamForOwner(User $user)
|
||||
{
|
||||
// The user we auth as must be the owner of the application.
|
||||
$auth = twitterAuthForUser($user);
|
||||
|
||||
if (have_option('apiroot')) {
|
||||
$stream = new TwitterSiteStream($auth, get_option_value('apiroot'));
|
||||
} else {
|
||||
$stream = new TwitterSiteStream($auth);
|
||||
}
|
||||
|
||||
// Pull Twitter user IDs for all users we want to pull data for
|
||||
$userIds = array();
|
||||
|
||||
$flink = new Foreign_link();
|
||||
$flink->service = TWITTER_SERVICE;
|
||||
$flink->find();
|
||||
|
||||
while ($flink->fetch()) {
|
||||
if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
|
||||
FOREIGN_NOTICE_RECV) {
|
||||
$userIds[] = $flink->foreign_id;
|
||||
}
|
||||
}
|
||||
|
||||
$stream->followUsers($userIds);
|
||||
return $stream;
|
||||
}
|
||||
|
||||
|
||||
$user = User::staticGet('nickname', $nickname);
|
||||
global $myuser;
|
||||
$myuser = $user;
|
||||
|
||||
if (have_option('all')) {
|
||||
$stream = siteStreamForOwner($user);
|
||||
} else {
|
||||
$stream = homeStreamForUser($user);
|
||||
}
|
||||
|
||||
|
||||
$stream->hookEvent('raw', function($data, $context) {
|
||||
common_log(LOG_INFO, json_encode($data) . ' for ' . json_encode($context));
|
||||
});
|
||||
$stream->hookEvent('friends', function($data, $context) {
|
||||
printf("Friend list: %s\n", implode(', ', $data->friends));
|
||||
});
|
||||
$stream->hookEvent('favorite', function($data, $context) {
|
||||
printf("%s favorited %s's notice: %s\n",
|
||||
$data->source->screen_name,
|
||||
$data->target->screen_name,
|
||||
$data->target_object->text);
|
||||
});
|
||||
$stream->hookEvent('unfavorite', function($data, $context) {
|
||||
printf("%s unfavorited %s's notice: %s\n",
|
||||
$data->source->screen_name,
|
||||
$data->target->screen_name,
|
||||
$data->target_object->text);
|
||||
});
|
||||
$stream->hookEvent('follow', function($data, $context) {
|
||||
printf("%s friended %s\n",
|
||||
$data->source->screen_name,
|
||||
$data->target->screen_name);
|
||||
});
|
||||
$stream->hookEvent('unfollow', function($data, $context) {
|
||||
printf("%s unfriended %s\n",
|
||||
$data->source->screen_name,
|
||||
$data->target->screen_name);
|
||||
});
|
||||
$stream->hookEvent('delete', function($data, $context) {
|
||||
printf("Deleted status notification: %s\n",
|
||||
$data->status->id);
|
||||
});
|
||||
$stream->hookEvent('scrub_geo', function($data, $context) {
|
||||
printf("Req to scrub geo data for user id %s up to status ID %s\n",
|
||||
$data->user_id,
|
||||
$data->up_to_status_id);
|
||||
});
|
||||
$stream->hookEvent('status', function($data, $context) {
|
||||
printf("Received status update from %s: %s\n",
|
||||
$data->user->screen_name,
|
||||
$data->text);
|
||||
|
||||
if (have_option('import')) {
|
||||
$importer = new TwitterImport();
|
||||
printf("\timporting...");
|
||||
$notice = $importer->importStatus($data);
|
||||
if ($notice) {
|
||||
global $myuser;
|
||||
Inbox::insertNotice($myuser->id, $notice->id);
|
||||
printf(" %s\n", $notice->id);
|
||||
} else {
|
||||
printf(" FAIL\n");
|
||||
}
|
||||
}
|
||||
});
|
||||
$stream->hookEvent('direct_message', function($data) {
|
||||
printf("Direct message from %s to %s: %s\n",
|
||||
$data->sender->screen_name,
|
||||
$data->recipient->screen_name,
|
||||
$data->text);
|
||||
});
|
||||
|
||||
class TwitterManager extends IoManager
|
||||
{
|
||||
function __construct(TwitterStreamReader $stream)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
function getSockets()
|
||||
{
|
||||
return $this->stream->getSockets();
|
||||
}
|
||||
|
||||
function handleInput($data)
|
||||
{
|
||||
$this->stream->handleInput($data);
|
||||
return true;
|
||||
}
|
||||
|
||||
function start()
|
||||
{
|
||||
$this->stream->connect();
|
||||
return true;
|
||||
}
|
||||
|
||||
function finish()
|
||||
{
|
||||
$this->stream->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function get()
|
||||
{
|
||||
throw new Exception('not a singleton');
|
||||
}
|
||||
}
|
||||
|
||||
class TwitterStreamMaster extends IoMaster
|
||||
{
|
||||
function __construct($id, $ioManager)
|
||||
{
|
||||
parent::__construct($id);
|
||||
$this->ioManager = $ioManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize IoManagers which are appropriate to this instance.
|
||||
*/
|
||||
function initManagers()
|
||||
{
|
||||
$this->instantiate($this->ioManager);
|
||||
}
|
||||
}
|
||||
|
||||
$master = new TwitterStreamMaster('TwitterStream', new TwitterManager($stream));
|
||||
$master->init();
|
||||
$master->service();
|
59
plugins/TwitterBridge/tweetctlqueuehandler.php
Normal file
59
plugins/TwitterBridge/tweetctlqueuehandler.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/*
|
||||
* 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') && !defined('LACONICA')) { exit(1); }
|
||||
|
||||
require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
|
||||
|
||||
/**
|
||||
* Queue handler to deal with incoming Twitter status updates, as retrieved by
|
||||
* TwitterDaemon (twitterdaemon.php).
|
||||
*
|
||||
* The queue handler passes the status through TwitterImporter for import into the
|
||||
* local database (if necessary), then adds the imported notice to the local inbox
|
||||
* of the attached Twitter user.
|
||||
*
|
||||
* Warning: the way we do inbox distribution manually means that realtime, XMPP, etc
|
||||
* don't work on Twitter-borne messages. When TwitterImporter is changed to handle
|
||||
* that correctly, we'll only need to do this once...?
|
||||
*/
|
||||
class TweetCtlQueueHandler extends QueueHandler
|
||||
{
|
||||
function transport()
|
||||
{
|
||||
return 'tweetctl';
|
||||
}
|
||||
|
||||
function handle($data)
|
||||
{
|
||||
// A user has activated or deactivated their Twitter bridge
|
||||
// import status.
|
||||
$action = $data['action'];
|
||||
$userId = $data['for_user'];
|
||||
|
||||
$tm = TwitterManager::get();
|
||||
if ($action == 'start') {
|
||||
$tm->startTwitterUser($userId);
|
||||
} else if ($action == 'stop') {
|
||||
$tm->stopTwitterUser($userId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
63
plugins/TwitterBridge/tweetinqueuehandler.php
Normal file
63
plugins/TwitterBridge/tweetinqueuehandler.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/*
|
||||
* 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') && !defined('LACONICA')) { exit(1); }
|
||||
|
||||
require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
|
||||
|
||||
/**
|
||||
* Queue handler to deal with incoming Twitter status updates, as retrieved by
|
||||
* TwitterDaemon (twitterdaemon.php).
|
||||
*
|
||||
* The queue handler passes the status through TwitterImporter for import into the
|
||||
* local database (if necessary), then adds the imported notice to the local inbox
|
||||
* of the attached Twitter user.
|
||||
*
|
||||
* Warning: the way we do inbox distribution manually means that realtime, XMPP, etc
|
||||
* don't work on Twitter-borne messages. When TwitterImporter is changed to handle
|
||||
* that correctly, we'll only need to do this once...?
|
||||
*/
|
||||
class TweetInQueueHandler extends QueueHandler
|
||||
{
|
||||
function transport()
|
||||
{
|
||||
return 'tweetin';
|
||||
}
|
||||
|
||||
function handle($data)
|
||||
{
|
||||
// JSON object with Twitter data
|
||||
$status = $data['status'];
|
||||
|
||||
// Twitter user ID this incoming data belongs to.
|
||||
$receiver = $data['for_user'];
|
||||
|
||||
$importer = new TwitterImport();
|
||||
$notice = $importer->importStatus($status);
|
||||
if ($notice) {
|
||||
$flink = Foreign_link::getByForeignID(TWITTER_SERVICE, $receiver);
|
||||
if ($flink) {
|
||||
// @fixme this should go through more regular channels?
|
||||
Inbox::insertNotice($flink->user_id, $notice->id);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
651
plugins/TwitterBridge/twitterimport.php
Normal file
651
plugins/TwitterBridge/twitterimport.php
Normal file
@ -0,0 +1,651 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* 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 Plugin
|
||||
* @package StatusNet
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @author Julien C <chaumond@gmail.com>
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2009-2010 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')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
|
||||
|
||||
/**
|
||||
* Encapsulation of the Twitter status -> notice incoming bridge import.
|
||||
* Is used by both the polling twitterstatusfetcher.php daemon, and the
|
||||
* in-progress streaming import.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package StatusNet
|
||||
* @author Zach Copley <zach@status.net>
|
||||
* @author Julien C <chaumond@gmail.com>
|
||||
* @author Brion Vibber <brion@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/
|
||||
* @link http://twitter.com/
|
||||
*/
|
||||
class TwitterImport
|
||||
{
|
||||
public function importStatus($status)
|
||||
{
|
||||
// Hacktastic: filter out stuff coming from this StatusNet
|
||||
$source = mb_strtolower(common_config('integration', 'source'));
|
||||
|
||||
if (preg_match("/$source/", mb_strtolower($status->source))) {
|
||||
common_debug($this->name() . ' - Skipping import of status ' .
|
||||
$status->id . ' with source ' . $source);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't save it if the user is protected
|
||||
// FIXME: save it but treat it as private
|
||||
if ($status->user->protected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$notice = $this->saveStatus($status);
|
||||
|
||||
return $notice;
|
||||
}
|
||||
|
||||
function name()
|
||||
{
|
||||
return get_class($this);
|
||||
}
|
||||
|
||||
function saveStatus($status)
|
||||
{
|
||||
$profile = $this->ensureProfile($status->user);
|
||||
|
||||
if (empty($profile)) {
|
||||
common_log(LOG_ERR, $this->name() .
|
||||
' - Problem saving notice. No associated Profile.');
|
||||
return null;
|
||||
}
|
||||
|
||||
$statusUri = $this->makeStatusURI($status->user->screen_name, $status->id);
|
||||
|
||||
// check to see if we've already imported the status
|
||||
$n2s = Notice_to_status::staticGet('status_id', $status->id);
|
||||
|
||||
if (!empty($n2s)) {
|
||||
common_log(
|
||||
LOG_INFO,
|
||||
$this->name() .
|
||||
" - Ignoring duplicate import: {$status->id}"
|
||||
);
|
||||
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(_m('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();
|
||||
|
||||
$notice->profile_id = $profile->id;
|
||||
$notice->uri = $statusUri;
|
||||
$notice->url = $statusUri;
|
||||
$notice->created = strftime(
|
||||
'%Y-%m-%d %H:%M:%S',
|
||||
strtotime($status->created_at)
|
||||
);
|
||||
|
||||
$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 = html_entity_decode($status->text, ENT_QUOTES, 'UTF-8');
|
||||
$notice->rendered = $this->linkify($status);
|
||||
|
||||
if (Event::handle('StartNoticeSave', array(&$notice))) {
|
||||
|
||||
$id = $notice->insert();
|
||||
|
||||
if (!$id) {
|
||||
common_log_db_error($notice, 'INSERT', __FILE__);
|
||||
common_log(LOG_ERR, $this->name() .
|
||||
' - Problem saving notice.');
|
||||
}
|
||||
|
||||
Event::handle('EndNoticeSave', array($notice));
|
||||
}
|
||||
|
||||
Notice_to_status::saveNew($notice->id, $status->id);
|
||||
|
||||
$this->saveStatusMentions($notice, $status);
|
||||
|
||||
$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.
|
||||
*
|
||||
* @param string $nickname local nickname of the Twitter user
|
||||
* @param string $profileurl the profile url
|
||||
*
|
||||
* @return mixed value the first Profile with that url, or null
|
||||
*/
|
||||
function getProfileByUrl($nickname, $profileurl)
|
||||
{
|
||||
$profile = new Profile();
|
||||
$profile->nickname = $nickname;
|
||||
$profile->profileurl = $profileurl;
|
||||
$profile->limit(1);
|
||||
|
||||
if ($profile->find()) {
|
||||
$profile->fetch();
|
||||
return $profile;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this Twitter status has already been imported
|
||||
*
|
||||
* @param Profile $profile Twitter user's local profile
|
||||
* @param string $statusUri URI of the status on Twitter
|
||||
*
|
||||
* @return mixed value a matching Notice or null
|
||||
*/
|
||||
function checkDupe($profile, $statusUri)
|
||||
{
|
||||
$notice = new Notice();
|
||||
$notice->uri = $statusUri;
|
||||
$notice->profile_id = $profile->id;
|
||||
$notice->limit(1);
|
||||
|
||||
if ($notice->find()) {
|
||||
$notice->fetch();
|
||||
return $notice;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function ensureProfile($user)
|
||||
{
|
||||
// check to see if there's already a profile for this user
|
||||
$profileurl = 'http://twitter.com/' . $user->screen_name;
|
||||
$profile = $this->getProfileByUrl($user->screen_name, $profileurl);
|
||||
|
||||
if (!empty($profile)) {
|
||||
common_debug($this->name() .
|
||||
" - Profile for $profile->nickname found.");
|
||||
|
||||
// Check to see if the user's Avatar has changed
|
||||
|
||||
$this->checkAvatar($user, $profile);
|
||||
return $profile;
|
||||
|
||||
} else {
|
||||
common_debug($this->name() . ' - Adding profile and remote profile ' .
|
||||
"for Twitter user: $profileurl.");
|
||||
|
||||
$profile = new Profile();
|
||||
$profile->query("BEGIN");
|
||||
|
||||
$profile->nickname = $user->screen_name;
|
||||
$profile->fullname = $user->name;
|
||||
$profile->homepage = $user->url;
|
||||
$profile->bio = $user->description;
|
||||
$profile->location = $user->location;
|
||||
$profile->profileurl = $profileurl;
|
||||
$profile->created = common_sql_now();
|
||||
|
||||
try {
|
||||
$id = $profile->insert();
|
||||
} catch(Exception $e) {
|
||||
common_log(LOG_WARNING, $this->name() . ' Couldn\'t insert profile - ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
common_log_db_error($profile, 'INSERT', __FILE__);
|
||||
$profile->query("ROLLBACK");
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for remote profile
|
||||
|
||||
$remote_pro = Remote_profile::staticGet('uri', $profileurl);
|
||||
|
||||
if (empty($remote_pro)) {
|
||||
$remote_pro = new Remote_profile();
|
||||
|
||||
$remote_pro->id = $id;
|
||||
$remote_pro->uri = $profileurl;
|
||||
$remote_pro->created = common_sql_now();
|
||||
|
||||
try {
|
||||
$rid = $remote_pro->insert();
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING, $this->name() . ' Couldn\'t save remote profile - ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($rid)) {
|
||||
common_log_db_error($profile, 'INSERT', __FILE__);
|
||||
$profile->query("ROLLBACK");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$profile->query("COMMIT");
|
||||
|
||||
$this->saveAvatars($user, $id);
|
||||
|
||||
return $profile;
|
||||
}
|
||||
}
|
||||
|
||||
function checkAvatar($twitter_user, $profile)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$path_parts = pathinfo($twitter_user->profile_image_url);
|
||||
|
||||
$newname = 'Twitter_' . $twitter_user->id . '_' .
|
||||
$path_parts['basename'];
|
||||
|
||||
$oldname = $profile->getAvatar(48)->filename;
|
||||
|
||||
if ($newname != $oldname) {
|
||||
common_debug($this->name() . ' - Avatar for Twitter user ' .
|
||||
"$profile->nickname has changed.");
|
||||
common_debug($this->name() . " - old: $oldname new: $newname");
|
||||
|
||||
$this->updateAvatars($twitter_user, $profile);
|
||||
}
|
||||
|
||||
if ($this->missingAvatarFile($profile)) {
|
||||
common_debug($this->name() . ' - Twitter user ' .
|
||||
$profile->nickname .
|
||||
' is missing one or more local avatars.');
|
||||
common_debug($this->name() ." - old: $oldname new: $newname");
|
||||
|
||||
$this->updateAvatars($twitter_user, $profile);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAvatars($twitter_user, $profile) {
|
||||
|
||||
global $config;
|
||||
|
||||
$path_parts = pathinfo($twitter_user->profile_image_url);
|
||||
|
||||
$img_root = substr($path_parts['basename'], 0, -11);
|
||||
$ext = $path_parts['extension'];
|
||||
$mediatype = $this->getMediatype($ext);
|
||||
|
||||
foreach (array('mini', 'normal', 'bigger') as $size) {
|
||||
$url = $path_parts['dirname'] . '/' .
|
||||
$img_root . '_' . $size . ".$ext";
|
||||
$filename = 'Twitter_' . $twitter_user->id . '_' .
|
||||
$img_root . "_$size.$ext";
|
||||
|
||||
$this->updateAvatar($profile->id, $size, $mediatype, $filename);
|
||||
$this->fetchAvatar($url, $filename);
|
||||
}
|
||||
}
|
||||
|
||||
function missingAvatarFile($profile) {
|
||||
foreach (array(24, 48, 73) as $size) {
|
||||
$filename = $profile->getAvatar($size)->filename;
|
||||
$avatarpath = Avatar::path($filename);
|
||||
if (file_exists($avatarpath) == FALSE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getMediatype($ext)
|
||||
{
|
||||
$mediatype = null;
|
||||
|
||||
switch (strtolower($ext)) {
|
||||
case 'jpg':
|
||||
$mediatype = 'image/jpg';
|
||||
break;
|
||||
case 'gif':
|
||||
$mediatype = 'image/gif';
|
||||
break;
|
||||
default:
|
||||
$mediatype = 'image/png';
|
||||
}
|
||||
|
||||
return $mediatype;
|
||||
}
|
||||
|
||||
function saveAvatars($user, $id)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$path_parts = pathinfo($user->profile_image_url);
|
||||
$ext = $path_parts['extension'];
|
||||
$end = strlen('_normal' . $ext);
|
||||
$img_root = substr($path_parts['basename'], 0, -($end+1));
|
||||
$mediatype = $this->getMediatype($ext);
|
||||
|
||||
foreach (array('mini', 'normal', 'bigger') as $size) {
|
||||
$url = $path_parts['dirname'] . '/' .
|
||||
$img_root . '_' . $size . ".$ext";
|
||||
$filename = 'Twitter_' . $user->id . '_' .
|
||||
$img_root . "_$size.$ext";
|
||||
|
||||
if ($this->fetchAvatar($url, $filename)) {
|
||||
$this->newAvatar($id, $size, $mediatype, $filename);
|
||||
} else {
|
||||
common_log(LOG_WARNING, $id() .
|
||||
" - Problem fetching Avatar: $url");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateAvatar($profile_id, $size, $mediatype, $filename) {
|
||||
|
||||
common_debug($this->name() . " - Updating avatar: $size");
|
||||
|
||||
$profile = Profile::staticGet($profile_id);
|
||||
|
||||
if (empty($profile)) {
|
||||
common_debug($this->name() . " - Couldn't get profile: $profile_id!");
|
||||
return;
|
||||
}
|
||||
|
||||
$sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
|
||||
$avatar = $profile->getAvatar($sizes[$size]);
|
||||
|
||||
// Delete the avatar, if present
|
||||
if ($avatar) {
|
||||
$avatar->delete();
|
||||
}
|
||||
|
||||
$this->newAvatar($profile->id, $size, $mediatype, $filename);
|
||||
}
|
||||
|
||||
function newAvatar($profile_id, $size, $mediatype, $filename)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$avatar = new Avatar();
|
||||
$avatar->profile_id = $profile_id;
|
||||
|
||||
switch($size) {
|
||||
case 'mini':
|
||||
$avatar->width = 24;
|
||||
$avatar->height = 24;
|
||||
break;
|
||||
case 'normal':
|
||||
$avatar->width = 48;
|
||||
$avatar->height = 48;
|
||||
break;
|
||||
default:
|
||||
// Note: Twitter's big avatars are a different size than
|
||||
// StatusNet's (StatusNet's = 96)
|
||||
$avatar->width = 73;
|
||||
$avatar->height = 73;
|
||||
}
|
||||
|
||||
$avatar->original = 0; // we don't have the original
|
||||
$avatar->mediatype = $mediatype;
|
||||
$avatar->filename = $filename;
|
||||
$avatar->url = Avatar::url($filename);
|
||||
|
||||
$avatar->created = common_sql_now();
|
||||
|
||||
try {
|
||||
$id = $avatar->insert();
|
||||
} catch (Exception $e) {
|
||||
common_log(LOG_WARNING, $this->name() . ' Couldn\'t insert avatar - ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
common_log_db_error($avatar, 'INSERT', __FILE__);
|
||||
return null;
|
||||
}
|
||||
|
||||
common_debug($this->name() .
|
||||
" - Saved new $size avatar for $profile_id.");
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a remote avatar image and save to local storage.
|
||||
*
|
||||
* @param string $url avatar source URL
|
||||
* @param string $filename bare local filename for download
|
||||
* @return bool true on success, false on failure
|
||||
*/
|
||||
function fetchAvatar($url, $filename)
|
||||
{
|
||||
common_debug($this->name() . " - Fetching Twitter avatar: $url");
|
||||
|
||||
$request = HTTPClient::start();
|
||||
$response = $request->get($url);
|
||||
if ($response->isOk()) {
|
||||
$avatarfile = Avatar::path($filename);
|
||||
$ok = file_put_contents($avatarfile, $response->getBody());
|
||||
if (!$ok) {
|
||||
common_log(LOG_WARNING, $this->name() .
|
||||
" - Couldn't open file $filename");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const URL = 1;
|
||||
const HASHTAG = 2;
|
||||
const MENTION = 3;
|
||||
|
||||
function linkify($status)
|
||||
{
|
||||
$text = $status->text;
|
||||
|
||||
if (empty($status->entities)) {
|
||||
common_log(LOG_WARNING, "No entities data for {$status->id}; trying to fake up links ourselves.");
|
||||
$text = common_replace_urls_callback($text, 'common_linkify');
|
||||
$text = preg_replace('/(^|\"\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/e', "'\\1#'.TwitterStatusFetcher::tagLink('\\2')", $text);
|
||||
$text = preg_replace('/(^|\s+)@([a-z0-9A-Z_]{1,64})/e', "'\\1@'.TwitterStatusFetcher::atLink('\\2')", $text);
|
||||
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 "#" . self::tagLink($object->text);
|
||||
}
|
||||
|
||||
function makeMentionLink($object)
|
||||
{
|
||||
return "@".self::atLink($object->screen_name, $object->name);
|
||||
}
|
||||
|
||||
static function tagLink($tag)
|
||||
{
|
||||
return "<a href='https://twitter.com/search?q=%23{$tag}' class='hashtag'>{$tag}</a>";
|
||||
}
|
||||
|
||||
static function atLink($screenName, $fullName=null)
|
||||
{
|
||||
if (!empty($fullName)) {
|
||||
return "<a href='http://twitter.com/{$screenName}' title='{$fullName}'>{$screenName}</a>";
|
||||
} else {
|
||||
return "<a href='http://twitter.com/{$screenName}'>{$screenName}</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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -285,6 +285,7 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
}
|
||||
|
||||
$original = clone($flink);
|
||||
$wasReceiving = (bool)($original->noticesync & FOREIGN_NOTICE_RECV);
|
||||
$flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
|
||||
$result = $flink->update($original);
|
||||
|
||||
@ -294,6 +295,19 @@ class TwittersettingsAction extends ConnectSettingsAction
|
||||
return;
|
||||
}
|
||||
|
||||
if ($wasReceiving xor $noticerecv) {
|
||||
$this->notifyDaemon($flink->foreign_id, $noticerecv);
|
||||
}
|
||||
|
||||
$this->showForm(_m('Twitter preferences saved.'), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the import daemon that we've updated a user's receive status.
|
||||
*/
|
||||
function notifyDaemon($twitterUserId, $receiving)
|
||||
{
|
||||
// todo... should use control signals rather than queues
|
||||
}
|
||||
|
||||
}
|
||||
|
285
plugins/TwitterBridge/twitterstreamreader.php
Normal file
285
plugins/TwitterBridge/twitterstreamreader.php
Normal file
@ -0,0 +1,285 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* 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 Plugin
|
||||
* @package StatusNet
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @copyright 2010 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/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for reading Twitter's User Streams and Site Streams
|
||||
* real-time streaming APIs.
|
||||
*
|
||||
* Caller can hook event callbacks for various types of messages;
|
||||
* the data from the stream and some context info will be passed
|
||||
* on to the callbacks.
|
||||
*/
|
||||
abstract class TwitterStreamReader extends JsonStreamReader
|
||||
{
|
||||
protected $callbacks = array();
|
||||
|
||||
function __construct(TwitterOAuthClient $auth, $baseUrl)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->oauth = $auth;
|
||||
}
|
||||
|
||||
public function connect($method, $params=array())
|
||||
{
|
||||
$url = $this->oAuthUrl($this->baseUrl . '/' . $method, $params);
|
||||
return parent::connect($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign our target URL with OAuth auth stuff.
|
||||
*
|
||||
* @param string $url
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
protected function oAuthUrl($url, $params=array())
|
||||
{
|
||||
// In an ideal world this would be better encapsulated. :)
|
||||
$request = OAuthRequest::from_consumer_and_token($this->oauth->consumer,
|
||||
$this->oauth->token, 'GET', $url, $params);
|
||||
$request->sign_request($this->oauth->sha1_method,
|
||||
$this->oauth->consumer, $this->oauth->token);
|
||||
|
||||
return $request->to_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event callback to receive notifications when things come in
|
||||
* over the wire.
|
||||
*
|
||||
* Callbacks should be in the form: function(object $data, array $context)
|
||||
* where $context may list additional data on some streams, such as the
|
||||
* user to whom the message should be routed.
|
||||
*
|
||||
* Available events:
|
||||
*
|
||||
* Messaging:
|
||||
*
|
||||
* 'status': $data contains a status update in standard Twitter JSON format.
|
||||
* $data->user: sending user in standard Twitter JSON format.
|
||||
* $data->text... etc
|
||||
*
|
||||
* 'direct_message': $data contains a direct message in standard Twitter JSON format.
|
||||
* $data->sender: sending user in standard Twitter JSON format.
|
||||
* $data->recipient: receiving user in standard Twitter JSON format.
|
||||
* $data->text... etc
|
||||
*
|
||||
*
|
||||
* Out of band events:
|
||||
*
|
||||
* 'follow': User has either started following someone, or is being followed.
|
||||
* $data->source: following user in standard Twitter JSON format.
|
||||
* $data->target: followed user in standard Twitter JSON format.
|
||||
*
|
||||
* 'favorite': Someone has favorited a status update.
|
||||
* $data->source: user doing the favoriting, in standard Twitter JSON format.
|
||||
* $data->target: user whose status was favorited, in standard Twitter JSON format.
|
||||
* $data->target_object: the favorited status update in standard Twitter JSON format.
|
||||
*
|
||||
* 'unfavorite': Someone has unfavorited a status update.
|
||||
* $data->source: user doing the unfavoriting, in standard Twitter JSON format.
|
||||
* $data->target: user whose status was unfavorited, in standard Twitter JSON format.
|
||||
* $data->target_object: the unfavorited status update in standard Twitter JSON format.
|
||||
*
|
||||
*
|
||||
* Meta information:
|
||||
*
|
||||
* 'friends':
|
||||
* $data->friends: array of user IDs of the current user's friends.
|
||||
*
|
||||
* 'delete': Advisory that a Twitter status has been deleted; nice clients
|
||||
* should follow suit.
|
||||
* $data->id: ID of status being deleted
|
||||
* $data->user_id: ID of its owning user
|
||||
*
|
||||
* 'scrub_geo': Advisory that a user is clearing geo data from their status
|
||||
* stream; nice clients should follow suit.
|
||||
* $data->user_id: ID of user
|
||||
* $data->up_to_status_id: any notice older than this should be scrubbed.
|
||||
*
|
||||
* 'limit': Advisory that tracking has hit a resource limit.
|
||||
* $data->track
|
||||
*
|
||||
* 'raw': receives the full JSON data for all message types.
|
||||
*
|
||||
* @param string $event
|
||||
* @param callable $callback
|
||||
*/
|
||||
public function hookEvent($event, $callback)
|
||||
{
|
||||
$this->callbacks[$event][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call event handler callbacks for the given event.
|
||||
*
|
||||
* @param string $event
|
||||
* @param mixed $arg1 ... one or more params to pass on
|
||||
*/
|
||||
protected function fireEvent($event, $arg1)
|
||||
{
|
||||
if (array_key_exists($event, $this->callbacks)) {
|
||||
$args = array_slice(func_get_args(), 1);
|
||||
foreach ($this->callbacks[$event] as $callback) {
|
||||
call_user_func_array($callback, $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleJson(stdClass $data)
|
||||
{
|
||||
$this->routeMessage($data);
|
||||
}
|
||||
|
||||
abstract protected function routeMessage(stdClass $data);
|
||||
|
||||
/**
|
||||
* Send the decoded JSON object out to any event listeners.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $context optional additional context data to pass on
|
||||
*/
|
||||
protected function handleMessage(stdClass $data, array $context=array())
|
||||
{
|
||||
$this->fireEvent('raw', $data, $context);
|
||||
|
||||
if (isset($data->text)) {
|
||||
$this->fireEvent('status', $data, $context);
|
||||
return;
|
||||
}
|
||||
if (isset($data->event)) {
|
||||
$this->fireEvent($data->event, $data, $context);
|
||||
return;
|
||||
}
|
||||
if (isset($data->friends)) {
|
||||
$this->fireEvent('friends', $data, $context);
|
||||
}
|
||||
|
||||
$knownMeta = array('delete', 'scrub_geo', 'limit', 'direct_message');
|
||||
foreach ($knownMeta as $key) {
|
||||
if (isset($data->$key)) {
|
||||
$this->fireEvent($key, $data->$key, $context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiuser stream listener for Twitter Site Streams API
|
||||
* http://dev.twitter.com/pages/site_streams
|
||||
*
|
||||
* The site streams API allows listening to updates for multiple users.
|
||||
* Pass in the user IDs to listen to in via followUser() -- note they
|
||||
* must each have a valid OAuth token for the application ID we're
|
||||
* connecting as.
|
||||
*
|
||||
* You'll need to be connecting with the auth keys for the user who
|
||||
* owns the application registration.
|
||||
*
|
||||
* The user each message is destined for will be passed to event handlers
|
||||
* in $context['for_user_id'].
|
||||
*/
|
||||
class TwitterSiteStream extends TwitterStreamReader
|
||||
{
|
||||
protected $userIds;
|
||||
|
||||
public function __construct(TwitterOAuthClient $auth, $baseUrl='http://betastream.twitter.com')
|
||||
{
|
||||
parent::__construct($auth, $baseUrl);
|
||||
}
|
||||
|
||||
public function connect($method='2b/site.json')
|
||||
{
|
||||
$params = array();
|
||||
if ($this->userIds) {
|
||||
$params['follow'] = implode(',', $this->userIds);
|
||||
}
|
||||
return parent::connect($method, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the users whose home streams should be pulled.
|
||||
* They all must have valid oauth tokens for this application.
|
||||
*
|
||||
* Must be called before connect().
|
||||
*
|
||||
* @param array $userIds
|
||||
*/
|
||||
function followUsers($userIds)
|
||||
{
|
||||
$this->userIds = $userIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each message in the site stream tells us which user ID it should be
|
||||
* routed to; we'll need that to let the caller know what to do.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
function routeMessage(stdClass $data)
|
||||
{
|
||||
$context = array(
|
||||
'source' => 'sitestream',
|
||||
'for_user' => $data->for_user
|
||||
);
|
||||
parent::handleMessage($data->message, $context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream listener for Twitter User Streams API
|
||||
* http://dev.twitter.com/pages/user_streams
|
||||
*
|
||||
* This will pull the home stream and additional events just for the user
|
||||
* we've authenticated as.
|
||||
*/
|
||||
class TwitterUserStream extends TwitterStreamReader
|
||||
{
|
||||
public function __construct(TwitterOAuthClient $auth, $baseUrl='https://userstream.twitter.com')
|
||||
{
|
||||
parent::__construct($auth, $baseUrl);
|
||||
}
|
||||
|
||||
public function connect($method='2/user.json')
|
||||
{
|
||||
return parent::connect($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Each message in the user stream is just ready to go.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
function routeMessage(stdClass $data)
|
||||
{
|
||||
$context = array(
|
||||
'source' => 'userstream'
|
||||
);
|
||||
parent::handleMessage($data, $context);
|
||||
}
|
||||
}
|
@ -128,25 +128,9 @@ class UserFlagPlugin extends Plugin
|
||||
*/
|
||||
function onEndProfilePageActionsElements(&$action, $profile)
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
if (!empty($user) && ($user->id != $profile->id)) {
|
||||
|
||||
$action->elementStart('li', 'entity_flag');
|
||||
|
||||
if (User_flag_profile::exists($profile->id, $user->id)) {
|
||||
// @todo FIXME: Add a title explaining what 'flagged' means?
|
||||
// TRANS: Message added to a profile if it has been flagged for review.
|
||||
$action->element('p', 'flagged', _('Flagged'));
|
||||
} else {
|
||||
$form = new FlagProfileForm($action, $profile,
|
||||
array('action' => 'showstream',
|
||||
'nickname' => $profile->nickname));
|
||||
$form->show();
|
||||
}
|
||||
|
||||
$action->elementEnd('li');
|
||||
}
|
||||
$this->showFlagButton($action, $profile,
|
||||
array('action' => 'showstream',
|
||||
'nickname' => $profile->nickname));
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -160,24 +144,42 @@ class UserFlagPlugin extends Plugin
|
||||
*/
|
||||
function onEndProfileListItemActionElements($item)
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
if (!empty($user)) {
|
||||
|
||||
list($action, $args) = $item->action->returnToArgs();
|
||||
|
||||
$args['action'] = $action;
|
||||
|
||||
$form = new FlagProfileForm($item->action, $item->profile, $args);
|
||||
|
||||
$item->action->elementStart('li', 'entity_flag');
|
||||
$form->show();
|
||||
$item->action->elementEnd('li');
|
||||
}
|
||||
list($action, $args) = $item->action->returnToArgs();
|
||||
$args['action'] = $action;
|
||||
$this->showFlagButton($item->action, $item->profile, $args);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually output a flag button. If the target profile has already been
|
||||
* flagged by the current user, a null-action faux button is shown.
|
||||
*
|
||||
* @param Action $action
|
||||
* @param Profile $profile
|
||||
* @param array $returnToArgs
|
||||
*/
|
||||
protected function showFlagButton($action, $profile, $returnToArgs)
|
||||
{
|
||||
$user = common_current_user();
|
||||
|
||||
if (!empty($user) && ($user->id != $profile->id)) {
|
||||
|
||||
$action->elementStart('li', 'entity_flag');
|
||||
|
||||
if (User_flag_profile::exists($profile->id, $user->id)) {
|
||||
// @todo FIXME: Add a title explaining what 'flagged' means?
|
||||
// TRANS: Message added to a profile if it has been flagged for review.
|
||||
$action->element('p', 'flagged', _m('Flagged'));
|
||||
} else {
|
||||
$form = new FlagProfileForm($action, $profile, $returnToArgs);
|
||||
$form->show();
|
||||
}
|
||||
|
||||
$action->elementEnd('li');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize any flagging buttons on the page
|
||||
*
|
||||
|
@ -79,21 +79,36 @@ class User_flag_profile extends Memcached_DataObject
|
||||
/**
|
||||
* return key definitions for DB_DataObject
|
||||
*
|
||||
* @return array key definitions
|
||||
* @return array of key names
|
||||
*/
|
||||
function keys()
|
||||
{
|
||||
return array('profile_id' => 'K', 'user_id' => 'K');
|
||||
return array_keys($this->keyTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* return key definitions for DB_DataObject
|
||||
*
|
||||
* @return array key definitions
|
||||
* @return array map of key definitions
|
||||
*/
|
||||
function keyTypes()
|
||||
{
|
||||
return $this->keys();
|
||||
return array('profile_id' => 'K', 'user_id' => '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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,13 +60,6 @@ class FlagprofileAction extends ProfileFormAction
|
||||
assert(!empty($user)); // checked above
|
||||
assert(!empty($this->profile)); // checked above
|
||||
|
||||
if (User_flag_profile::exists($this->profile->id,
|
||||
$user->id)) {
|
||||
// TRANS: Client error when setting flag that has already been set for a profile.
|
||||
$this->clientError(_m('Flag already exists.'));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -104,7 +97,13 @@ class FlagprofileAction extends ProfileFormAction
|
||||
|
||||
// throws an exception on error
|
||||
|
||||
User_flag_profile::create($user->id, $this->profile->id);
|
||||
if (User_flag_profile::exists($this->profile->id,
|
||||
$user->id)) {
|
||||
// We'll return to the profile page (or return the updated AJAX form)
|
||||
// showing the current state, so no harm done.
|
||||
} else {
|
||||
User_flag_profile::create($user->id, $this->profile->id);
|
||||
}
|
||||
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->ajaxResults();
|
||||
|
26
plugins/UserLimit/locale/fi/LC_MESSAGES/UserLimit.po
Normal file
26
plugins/UserLimit/locale/fi/LC_MESSAGES/UserLimit.po
Normal file
@ -0,0 +1,26 @@
|
||||
# Translation of StatusNet - UserLimit to Finnish (Suomi)
|
||||
# Expored from translatewiki.net
|
||||
#
|
||||
# Author: Centerlink
|
||||
# --
|
||||
# This file is distributed under the same license as the StatusNet package.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: StatusNet - UserLimit\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2010-11-02 22:51+0000\n"
|
||||
"PO-Revision-Date: 2010-11-02 22:55:20+0000\n"
|
||||
"Language-Team: Finnish <http://translatewiki.net/wiki/Portal:fi>\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-POT-Import-Date: 2010-10-29 16:14:14+0000\n"
|
||||
"X-Generator: MediaWiki 1.17alpha (r75875); Translate extension (2010-09-17)\n"
|
||||
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
|
||||
"X-Language-Code: fi\n"
|
||||
"X-Message-Group: #out-statusnet-plugin-userlimit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: UserLimitPlugin.php:89
|
||||
msgid "Limit the number of users who can register."
|
||||
msgstr "Rajoita niiden käyttäjien lukumäärää, jotka voivat rekisteröityä."
|
@ -57,7 +57,8 @@ function getActivityStreamDocument()
|
||||
throw new Exception("File '$filename' not readable.");
|
||||
}
|
||||
|
||||
printfv(_("Getting backup from file '$filename'.")."\n");
|
||||
// TRANS: Commandline script output. %s is the filename that contains a backup for a user.
|
||||
printfv(_("Getting backup from file '%s'.")."\n",$filename);
|
||||
|
||||
$xml = file_get_contents($filename);
|
||||
|
||||
@ -79,19 +80,22 @@ function importActivityStream($user, $doc)
|
||||
|
||||
if (!empty($subjectEl)) {
|
||||
$subject = new ActivityObject($subjectEl);
|
||||
printfv(_("Backup file for user %s (%s)")."\n", $subject->id, Ostatus_profile::getActivityObjectNickname($subject));
|
||||
// TRANS: Commandline script output. %1$s is the subject ID, %2$s is the subject nickname.
|
||||
printfv(_("Backup file for user %1$s (%2$s)")."\n", $subject->id, Ostatus_profile::getActivityObjectNickname($subject));
|
||||
} else {
|
||||
throw new Exception("Feed doesn't have an <activity:subject> element.");
|
||||
}
|
||||
|
||||
if (is_null($user)) {
|
||||
// TRANS: Commandline script output.
|
||||
printfv(_("No user specified; using backup user.")."\n");
|
||||
$user = userFromSubject($subject);
|
||||
}
|
||||
|
||||
$entries = $feed->getElementsByTagNameNS(Activity::ATOM, 'entry');
|
||||
|
||||
printfv(_("%d entries in backup.")."\n", $entries->length);
|
||||
// TRANS: Commandline script output. %d is the number of entries in the activity stream in backup; used for plural.
|
||||
printfv(_m("%d entry in backup.","%d entries in backup.",$entries->length)."\n", $entries->length);
|
||||
|
||||
for ($i = $entries->length - 1; $i >= 0; $i--) {
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user