Create IM plugin, Pluginize XMPP, Create AIM plugin

This commit is contained in:
Craig Andrews 2010-01-23 01:25:27 -05:00
parent 5224c7d6c2
commit e9995b0f6a
42 changed files with 4968 additions and 1416 deletions

View File

@ -699,3 +699,21 @@ StartShowContentLicense: Showing the default license for content
EndShowContentLicense: Showing the default license for content
- $action: the current action
GetImTransports: Get IM transports that are available
- &$transports: append your transport to this array like so: $transports[transportName]=array('display'=>display)
NormalizeImScreenname: Normalize an IM screenname
- $transport: transport the screenname is on
- &$screenname: screenname to be normalized
ValidateImScreenname: Validate an IM screenname
- $transport: transport the screenname is on
- $screenname: screenname to be validated
- $valid: is the screenname valid?
SendImConfirmationCode: Send a confirmation code to confirm a user owns an IM screenname
- $transport: transport the screenname exists on
- $screenname: screenname being confirmed
- $code: confirmation code for confirmation URL
- $user: user requesting the confirmation

View File

@ -119,10 +119,16 @@ class ApiAccountUpdateDeliveryDeviceAction extends ApiAuthAction
if (strtolower($this->device) == 'sms') {
$this->user->smsnotify = true;
} elseif (strtolower($this->device) == 'im') {
$this->user->jabbernotify = true;
//TODO IM is pluginized now, so what should we do?
//Enable notifications for all IM plugins?
//For now, don't do anything
//$this->user->jabbernotify = true;
} elseif (strtolower($this->device == 'none')) {
$this->user->smsnotify = false;
$this->user->jabbernotify = false;
//TODO IM is pluginized now, so what should we do?
//Disable notifications for all IM plugins?
//For now, don't do anything
//$this->user->jabbernotify = false;
}
$result = $this->user->update($original);

View File

@ -49,7 +49,7 @@ class ConfirmaddressAction extends Action
{
/** type of confirmation. */
var $type = null;
var $address;
/**
* Accept a confirmation code
@ -86,37 +86,75 @@ class ConfirmaddressAction extends Action
return;
}
$type = $confirm->address_type;
if (!in_array($type, array('email', 'jabber', 'sms'))) {
$transports = array();
Event::handle('GetImTransports', array(&$transports));
if (!in_array($type, array('email', 'sms')) && !in_array($type, array_keys($transports))) {
$this->serverError(sprintf(_('Unrecognized address type %s'), $type));
return;
}
if ($cur->$type == $confirm->address) {
$this->clientError(_('That address has already been confirmed.'));
return;
}
$this->address = $confirm->address;
$cur->query('BEGIN');
if (in_array($type, array('email', 'sms')))
{
if ($cur->$type == $confirm->address) {
$this->clientError(_('That address has already been confirmed.'));
return;
}
$orig_user = clone($cur);
$orig_user = clone($cur);
$cur->$type = $confirm->address;
$cur->$type = $confirm->address;
if ($type == 'sms') {
$cur->carrier = ($confirm->address_extra)+0;
$carrier = Sms_carrier::staticGet($cur->carrier);
$cur->smsemail = $carrier->toEmailAddress($cur->sms);
}
if ($type == 'sms') {
$cur->carrier = ($confirm->address_extra)+0;
$carrier = Sms_carrier::staticGet($cur->carrier);
$cur->smsemail = $carrier->toEmailAddress($cur->sms);
}
$result = $cur->updateKeys($orig_user);
$result = $cur->updateKeys($orig_user);
if (!$result) {
common_log_db_error($cur, 'UPDATE', __FILE__);
$this->serverError(_('Couldn\'t update user.'));
return;
}
if (!$result) {
common_log_db_error($cur, 'UPDATE', __FILE__);
$this->serverError(_('Couldn\'t update user.'));
return;
}
if ($type == 'email') {
$cur->emailChanged();
}
} else {
$user_im_prefs = new User_im_prefs();
$user_im_prefs->transport = $confirm->address_type;
$user_im_prefs->user_id = $cur->id;
if ($user_im_prefs->find() && $user_im_prefs->fetch()) {
if($user_im_prefs->screenname == $confirm->address){
$this->clientError(_('That address has already been confirmed.'));
return;
}
$user_im_prefs->screenname = $confirm->address;
$result = $user_im_prefs->update();
if (!$result) {
common_log_db_error($user_im_prefs, 'UPDATE', __FILE__);
$this->serverError(_('Couldn\'t update user im preferences.'));
return;
}
}else{
$user_im_prefs = new User_im_prefs();
$user_im_prefs->screenname = $confirm->address;
$user_im_prefs->transport = $confirm->address_type;
$user_im_prefs->user_id = $cur->id;
$result = $user_im_prefs->insert();
if (!$result) {
common_log_db_error($user_im_prefs, 'INSERT', __FILE__);
$this->serverError(_('Couldn\'t insert user im preferences.'));
return;
}
}
if ($type == 'email') {
$cur->emailChanged();
}
$result = $confirm->delete();
@ -128,8 +166,6 @@ class ConfirmaddressAction extends Action
}
$cur->query('COMMIT');
$this->type = $type;
$this->showPage();
}
@ -153,11 +189,10 @@ class ConfirmaddressAction extends Action
function showContent()
{
$cur = common_current_user();
$type = $this->type;
$this->element('p', null,
sprintf(_('The address "%s" has been '.
'confirmed for your account.'),
$cur->$type));
$this->address));
}
}

View File

@ -31,9 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/connectsettingsaction.php';
require_once INSTALLDIR.'/lib/jabber.php';
/**
* Settings for Jabber/XMPP integration
*
@ -68,8 +65,8 @@ class ImsettingsAction extends ConnectSettingsAction
function getInstructions()
{
return _('You can send and receive notices through '.
'Jabber/GTalk [instant messages](%%doc.im%%). '.
'Configure your address and settings below.');
'instant messaging [instant messages](%%doc.im%%). '.
'Configure your addresses and settings below.');
}
/**
@ -84,85 +81,108 @@ class ImsettingsAction extends ConnectSettingsAction
function showContent()
{
if (!common_config('xmpp', 'enabled')) {
$transports = array();
Event::handle('GetImTransports', array(&$transports));
if (! $transports) {
$this->element('div', array('class' => 'error'),
_('IM is not available.'));
return;
}
$user = common_current_user();
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_im',
'class' => 'form_settings',
'action' =>
common_local_url('imsettings')));
$this->elementStart('fieldset', array('id' => 'settings_im_address'));
$this->element('legend', null, _('Address'));
$this->hidden('token', common_session_token());
if ($user->jabber) {
$this->element('p', 'form_confirmed', $user->jabber);
$this->element('p', 'form_note',
_('Current confirmed Jabber/GTalk address.'));
$this->hidden('jabber', $user->jabber);
$this->submit('remove', _('Remove'));
} else {
$confirm = $this->getConfirmation();
if ($confirm) {
$this->element('p', 'form_unconfirmed', $confirm->address);
$user_im_prefs_by_transport = array();
foreach($transports as $transport=>$transport_info)
{
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_im',
'class' => 'form_settings',
'action' =>
common_local_url('imsettings')));
$this->elementStart('fieldset', array('id' => 'settings_im_address'));
$this->element('legend', null, $transport_info['display']);
$this->hidden('token', common_session_token());
$this->hidden('transport', $transport);
if ($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $transport, 'user_id' => $user->id) )) {
$user_im_prefs_by_transport[$transport] = $user_im_prefs;
$this->element('p', 'form_confirmed', $user_im_prefs->screenname);
$this->element('p', 'form_note',
sprintf(_('Awaiting confirmation on this address. '.
'Check your Jabber/GTalk account for a '.
'message with further instructions. '.
'(Did you add %s to your buddy list?)'),
jabber_daemon_address()));
$this->hidden('jabber', $confirm->address);
$this->submit('cancel', _('Cancel'));
sprintf(_('Current confirmed %s address.'),$transport_info['display']));
$this->hidden('screenname', $user_im_prefs->screenname);
$this->submit('remove', _('Remove'));
} else {
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('jabber', _('IM address'),
($this->arg('jabber')) ? $this->arg('jabber') : null,
sprintf(_('Jabber or GTalk address, '.
'like "UserName@example.org". '.
'First, make sure to add %s to your '.
'buddy list in your IM client or on GTalk.'),
jabber_daemon_address()));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('add', _('Add'));
$confirm = $this->getConfirmation($transport);
if ($confirm) {
$this->element('p', 'form_unconfirmed', $confirm->address);
$this->element('p', 'form_note',
sprintf(_('Awaiting confirmation on this address. '.
'Check your %s account for a '.
'message with further instructions.'),
$transport_info['display']));
$this->hidden('screenname', $confirm->address);
$this->submit('cancel', _('Cancel'));
} else {
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('screenname', _('IM address'),
($this->arg('screenname')) ? $this->arg('screenname') : null,
sprintf(_('%s screenname.'),
$transport_info['display']));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('add', _('Add'));
}
}
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
$this->elementEnd('fieldset');
$this->elementStart('fieldset', array('id' => 'settings_im_preferences'));
$this->element('legend', null, _('Preferences'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->checkbox('jabbernotify',
_('Send me notices through Jabber/GTalk.'),
$user->jabbernotify);
$this->elementEnd('li');
$this->elementStart('li');
$this->checkbox('updatefrompresence',
_('Post a notice when my Jabber/GTalk status changes.'),
$user->updatefrompresence);
$this->elementEnd('li');
$this->elementStart('li');
$this->checkbox('jabberreplies',
_('Send me replies through Jabber/GTalk '.
'from people I\'m not subscribed to.'),
$user->jabberreplies);
$this->elementEnd('li');
$this->elementStart('li');
$this->checkbox('jabbermicroid',
_('Publish a MicroID for my Jabber/GTalk address.'),
$user->jabbermicroid);
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('save', _('Save'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
if($user_im_prefs_by_transport)
{
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_im',
'class' => 'form_settings',
'action' =>
common_local_url('imsettings')));
$this->elementStart('fieldset', array('id' => 'settings_im_preferences'));
$this->element('legend', null, _('Preferences'));
$this->hidden('token', common_session_token());
$this->elementStart('table');
$this->elementStart('tr');
$this->element('th', null, _('Preferences'));
foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs)
{
$this->element('th', null, $transports[$transport]['display']);
}
$this->elementEnd('tr');
$preferences = array(
array('name'=>'notify', 'description'=>_('Send me notices')),
array('name'=>'updatefrompresence', 'description'=>_('Post a notice when my status changes.')),
array('name'=>'replies', 'description'=>_('Send me replies '.
'from people I\'m not subscribed to.')),
array('name'=>'microid', 'description'=>_('Publish a MicroID'))
);
foreach($preferences as $preference)
{
$this->elementStart('tr');
foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs)
{
$preference_name = $preference['name'];
$this->elementStart('td');
$this->checkbox($transport . '_' . $preference['name'],
$preference['description'],
$user_im_prefs->$preference_name);
$this->elementEnd('td');
}
$this->elementEnd('tr');
}
$this->elementEnd('table');
$this->submit('save', _('Save'));
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
}
/**
@ -171,14 +191,14 @@ class ImsettingsAction extends ConnectSettingsAction
* @return Confirm_address address object for this user
*/
function getConfirmation()
function getConfirmation($transport)
{
$user = common_current_user();
$confirm = new Confirm_address();
$confirm->user_id = $user->id;
$confirm->address_type = 'jabber';
$confirm->address_type = $transport;
if ($confirm->find(true)) {
return $confirm;
@ -232,35 +252,31 @@ class ImsettingsAction extends ConnectSettingsAction
function savePreferences()
{
$jabbernotify = $this->boolean('jabbernotify');
$updatefrompresence = $this->boolean('updatefrompresence');
$jabberreplies = $this->boolean('jabberreplies');
$jabbermicroid = $this->boolean('jabbermicroid');
$user = common_current_user();
assert(!is_null($user)); // should already be checked
$user_im_prefs = new User_im_prefs();
$user_im_prefs->user_id = $user->id;
if($user_im_prefs->find() && $user_im_prefs->fetch())
{
$preferences = array('notify', 'updatefrompresence', 'replies', 'microid');
$user_im_prefs->query('BEGIN');
do
{
$original = clone($user_im_prefs);
foreach($preferences as $preference)
{
$user_im_prefs->$preference = $this->boolean($user_im_prefs->transport . '_' . $preference);
}
$result = $user_im_prefs->update($original);
$user->query('BEGIN');
$original = clone($user);
$user->jabbernotify = $jabbernotify;
$user->updatefrompresence = $updatefrompresence;
$user->jabberreplies = $jabberreplies;
$user->jabbermicroid = $jabbermicroid;
$result = $user->update($original);
if ($result === false) {
common_log_db_error($user, 'UPDATE', __FILE__);
$this->serverError(_('Couldn\'t update user.'));
return;
if ($result === false) {
common_log_db_error($user, 'UPDATE', __FILE__);
$this->serverError(_('Couldn\'t update IM preferences.'));
return;
}
}while($user_im_prefs->fetch());
$user_im_prefs->query('COMMIT');
}
$user->query('COMMIT');
$this->showForm(_('Preferences saved.'), true);
}
@ -268,7 +284,7 @@ class ImsettingsAction extends ConnectSettingsAction
* Sends a confirmation to the address given
*
* Stores a confirmation record and sends out a
* Jabber message with the confirmation info.
* message with the confirmation info.
*
* @return void
*/
@ -277,36 +293,41 @@ class ImsettingsAction extends ConnectSettingsAction
{
$user = common_current_user();
$jabber = $this->trimmed('jabber');
$screenname = $this->trimmed('screenname');
$transport = $this->trimmed('transport');
// Some validation
if (!$jabber) {
$this->showForm(_('No Jabber ID.'));
if (!$screenname) {
$this->showForm(_('No screenname.'));
return;
}
$jabber = jabber_normalize_jid($jabber);
if (!$jabber) {
$this->showForm(_('Cannot normalize that Jabber ID'));
if (!$transport) {
$this->showForm(_('No transport.'));
return;
}
if (!jabber_valid_base_jid($jabber)) {
$this->showForm(_('Not a valid Jabber ID'));
Event::handle('NormalizeImScreenname', array($transport, &$screenname));
if (!$screenname) {
$this->showForm(_('Cannot normalize that screenname'));
return;
} else if ($user->jabber == $jabber) {
$this->showForm(_('That is already your Jabber ID.'));
}
$valid = false;
Event::handle('ValidateImScreenname', array($transport, $screenname, &$valid));
if (!$valid) {
$this->showForm(_('Not a valid screenname'));
return;
} else if ($this->jabberExists($jabber)) {
$this->showForm(_('Jabber ID already belongs to another user.'));
} else if ($this->screennameExists($transport, $screenname)) {
$this->showForm(_('Screenname already belongs to another user.'));
return;
}
$confirm = new Confirm_address();
$confirm->address = $jabber;
$confirm->address_type = 'jabber';
$confirm->address = $screenname;
$confirm->address_type = $transport;
$confirm->user_id = $user->id;
$confirm->code = common_confirmation_code(64);
$confirm->sent = common_sql_now();
@ -320,15 +341,10 @@ class ImsettingsAction extends ConnectSettingsAction
return;
}
jabber_confirm_address($confirm->code,
$user->nickname,
$jabber);
Event::handle('SendImConfirmationCode', array($transport, $screenname, $confirm->code, $user));
$msg = sprintf(_('A confirmation code was sent '.
'to the IM address you added. '.
'You must approve %s for '.
'sending messages to you.'),
jabber_daemon_address());
$msg = _('A confirmation code was sent '.
'to the IM address you added.');
$this->showForm($msg, true);
}
@ -343,15 +359,16 @@ class ImsettingsAction extends ConnectSettingsAction
function cancelConfirmation()
{
$jabber = $this->arg('jabber');
$screenname = $this->trimmed('screenname');
$transport = $this->trimmed('transport');
$confirm = $this->getConfirmation();
$confirm = $this->getConfirmation($transport);
if (!$confirm) {
$this->showForm(_('No pending confirmation to cancel.'));
return;
}
if ($confirm->address != $jabber) {
if ($confirm->address != $screenname) {
$this->showForm(_('That is the wrong IM address.'));
return;
}
@ -360,7 +377,7 @@ class ImsettingsAction extends ConnectSettingsAction
if (!$result) {
common_log_db_error($confirm, 'DELETE', __FILE__);
$this->serverError(_('Couldn\'t delete email confirmation.'));
$this->serverError(_('Couldn\'t delete confirmation.'));
return;
}
@ -379,29 +396,25 @@ class ImsettingsAction extends ConnectSettingsAction
{
$user = common_current_user();
$jabber = $this->arg('jabber');
$screenname = $this->trimmed('screenname');
$transport = $this->trimmed('transport');
// Maybe an old tab open...?
if ($user->jabber != $jabber) {
$this->showForm(_('That is not your Jabber ID.'));
$user_im_prefs = new User_im_prefs();
$user_im_prefs->user_id = $user->id;
if(! ($user_im_prefs->find() && $user_im_prefs->fetch())) {
$this->showForm(_('That is not your screenname.'));
return;
}
$user->query('BEGIN');
$original = clone($user);
$user->jabber = null;
$result = $user->updateKeys($original);
$result = $user_im_prefs->delete();
if (!$result) {
common_log_db_error($user, 'UPDATE', __FILE__);
$this->serverError(_('Couldn\'t update user.'));
$this->serverError(_('Couldn\'t update user im prefs.'));
return;
}
$user->query('COMMIT');
// XXX: unsubscribe to the old address
@ -409,25 +422,27 @@ class ImsettingsAction extends ConnectSettingsAction
}
/**
* Does this Jabber ID exist?
* Does this screenname exist?
*
* Checks if we already have another user with this address.
*
* @param string $jabber Address to check
* @param string $transport Transport to check
* @param string $screenname Screenname to check
*
* @return boolean whether the Jabber ID exists
* @return boolean whether the screenname exists
*/
function jabberExists($jabber)
function screennameExists($transport, $screenname)
{
$user = common_current_user();
$other = User::staticGet('jabber', $jabber);
if (!$other) {
$user_im_prefs = new User_im_prefs();
$user_im_prefs->transport = $transport;
$user_im_prefs->screenname = $screenname;
if($user_im_prefs->find() && $user_im_prefs->fetch()){
return true;
}else{
return false;
} else {
return $other->id != $user->id;
}
}
}

View File

@ -275,12 +275,6 @@ class ShownoticeAction extends OwnerDesignAction
'content' => $id->toString()));
}
if ($user->jabbermicroid && $user->jabber && $this->notice->uri) {
$id = new Microid('xmpp:', $user->jabber,
$this->notice->uri);
$this->element('meta', array('name' => 'microid',
'content' => $id->toString()));
}
$this->element('link',array('rel'=>'alternate',
'type'=>'application/json+oembed',
'href'=>common_local_url(

View File

@ -166,12 +166,6 @@ class ShowstreamAction extends ProfileAction
$this->element('meta', array('name' => 'microid',
'content' => $id->toString()));
}
if ($this->user->jabbermicroid && $this->user->jabber && $this->profile->profileurl) {
$id = new Microid('xmpp:'.$this->user->jabber,
$this->selfUrl());
$this->element('meta', array('name' => 'microid',
'content' => $id->toString()));
}
// See https://wiki.mozilla.org/Microsummaries

View File

@ -48,11 +48,6 @@ class User extends Memcached_DataObject
public $language; // varchar(50)
public $timezone; // varchar(50)
public $emailpost; // tinyint(1) default_1
public $jabber; // varchar(255) unique_key
public $jabbernotify; // tinyint(1)
public $jabberreplies; // tinyint(1)
public $jabbermicroid; // tinyint(1) default_1
public $updatefrompresence; // tinyint(1)
public $sms; // varchar(64) unique_key
public $carrier; // int(4)
public $smsnotify; // tinyint(1)
@ -92,7 +87,7 @@ class User extends Memcached_DataObject
function updateKeys(&$orig)
{
$parts = array();
foreach (array('nickname', 'email', 'jabber', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) {
foreach (array('nickname', 'email', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) {
if (strcmp($this->$k, $orig->$k) != 0) {
$parts[] = $k . ' = ' . $this->_quote($this->$k);
}

71
classes/User_im_prefs.php Normal file
View File

@ -0,0 +1,71 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Data class for user IM preferences
*
* 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 Data
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 StatusNet Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
class User_im_prefs extends Memcached_DataObject
{
###START_AUTOCODE
/* the code below is auto generated do not remove the above tag */
public $__table = 'user_im_prefs'; // table name
public $user_id; // int(4) primary_key not_null
public $screenname; // varchar(255) not_null
public $transport; // varchar(255) not_null
public $notify; // tinyint(1)
public $replies; // tinyint(1)
public $microid; // tinyint(1)
public $updatefrompresence; // tinyint(1)
public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00
public $modified; // timestamp not_null default_CURRENT_TIMESTAMP
/* Static get */
function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_im_prefs',$k,$v); }
function pkeyGet($kv)
{
return Memcached_DataObject::pkeyGet('User_im_prefs', $kv);
}
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
/*
DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function.
In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but
DB_DataObject's sequenceKey() will incorrectly think it is. Then, since the sequenceKey() is a numeric
type, but is not set to autoincrement in the database, DB_DataObject will create a _seq table and
manage the sequence itself. This is not the correct behavior for the user_id in this class.
So we override that incorrect behavior, and simply say there is no sequence key.
*/
function sequenceKey()
{
return array(false,false);
}
}

View File

@ -540,11 +540,6 @@ emailmicroid = 17
language = 2
timezone = 2
emailpost = 17
jabber = 2
jabbernotify = 17
jabberreplies = 17
jabbermicroid = 17
updatefrompresence = 17
sms = 2
carrier = 1
smsnotify = 17
@ -564,7 +559,6 @@ id = K
nickname = U
email = U
incomingemail = U
jabber = U
sms = U
uri = U
@ -616,3 +610,19 @@ modified = 384
[user_location_prefs__keys]
user_id = K
[user_im_prefs]
user_id = 129
screenname = 130
transport = 130
notify = 17
replies = 17
microid = 17
updatefrompresence = 17
created = 142
modified = 384
[user_im_prefs__keys]
user_id = K
transport = K
transport = U
screenname = U

View File

@ -62,11 +62,6 @@ create table user (
language varchar(50) comment 'preferred language',
timezone varchar(50) comment 'timezone',
emailpost tinyint default 1 comment 'Post by email',
jabber varchar(255) unique key comment 'jabber ID for notices',
jabbernotify tinyint default 0 comment 'whether to send notices to jabber',
jabberreplies tinyint default 0 comment 'whether to send notices to jabber on replies',
jabbermicroid tinyint default 1 comment 'whether to publish xmpp microid',
updatefrompresence tinyint default 0 comment 'whether to record updates from Jabber presence notices',
sms varchar(64) unique key comment 'sms phone number',
carrier integer comment 'foreign key to sms_carrier' references sms_carrier (id),
smsnotify tinyint default 0 comment 'whether to send notices to SMS',
@ -259,9 +254,9 @@ create table oid_nonces (
create table confirm_address (
code varchar(32) not null primary key comment 'good random code',
user_id integer not null comment 'user who requested confirmation' references user (id),
address varchar(255) not null comment 'address (email, Jabber, SMS, etc.)',
address varchar(255) not null comment 'address (email, xmpp, SMS, etc.)',
address_extra varchar(255) not null comment 'carrier ID, for SMS',
address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")',
address_type varchar(8) not null comment 'address type ("email", "xmpp", "sms")',
claimed datetime comment 'date this was claimed for queueing',
sent datetime comment 'date this was sent for queueing',
modified timestamp comment 'date this record was modified'
@ -276,7 +271,7 @@ create table remember_me (
create table queue_item (
id integer auto_increment primary key comment 'unique identifier',
frame blob not null comment 'data: object reference or opaque string',
transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...',
transport varchar(8) not null comment 'queue for what? "email", "xmpp", "sms", "irc", ...',
created datetime not null comment 'date this record was created',
claimed datetime comment 'date this item was claimed',
@ -348,7 +343,7 @@ create table invitation (
code varchar(32) not null primary key comment 'random code for an invitation',
user_id int not null comment 'who sent the invitation' references user (id),
address varchar(255) not null comment 'invitation sent to',
address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")',
address_type varchar(8) not null comment 'address type ("email", "xmpp", "sms")',
created datetime not null comment 'date this record was created',
index invitation_address_idx (address, address_type),
@ -633,3 +628,18 @@ create table inbox (
constraint primary key (user_id)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
create table user_im_prefs (
user_id integer not null comment 'user' references user (id),
screenname varchar(255) not null comment 'screenname on this service',
transport varchar(255) not null comment 'transport (ex xmpp, aim)',
notify tinyint(1) not null default 0 comment 'Notify when a new notice is sent',
replies tinyint(1) not null default 0 comment 'Send replies from people not subscribed to',
microid tinyint(1) not null default 1 comment 'Publish a MicroID',
updatefrompresence tinyint(1) not null default 0 comment 'Send replies from people not subscribed to.',
created timestamp not null DEFAULT CURRENT_TIMESTAMP comment 'date this record was created',
modified timestamp comment 'date this record was modified',
constraint primary key (user_id, transport),
constraint unique key `transport_screenname_key` ( `transport` , `screenname` )
);

View File

@ -47,63 +47,6 @@ class Channel
}
}
class XMPPChannel extends Channel
{
var $conn = null;
function source()
{
return 'xmpp';
}
function __construct($conn)
{
$this->conn = $conn;
}
function on($user)
{
return $this->set_notify($user, 1);
}
function off($user)
{
return $this->set_notify($user, 0);
}
function output($user, $text)
{
$text = '['.common_config('site', 'name') . '] ' . $text;
jabber_send_message($user->jabber, $text);
}
function error($user, $text)
{
$text = '['.common_config('site', 'name') . '] ' . $text;
jabber_send_message($user->jabber, $text);
}
function set_notify(&$user, $notify)
{
$orig = clone($user);
$user->jabbernotify = $notify;
$result = $user->update($orig);
if (!$result) {
$last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
common_log(LOG_ERR,
'Could not set notify flag to ' . $notify .
' for user ' . common_log_objstring($user) .
': ' . $last_error->message);
return false;
} else {
common_log(LOG_INFO,
'User ' . $user->nickname . ' set notify flag to ' . $notify);
return true;
}
}
}
class WebChannel extends Channel
{
var $out = null;

View File

@ -596,7 +596,7 @@ class OffCommand extends Command
}
function execute($channel)
{
if ($other) {
if ($this->other) {
$channel->error($this->user, _("Command not yet implemented."));
} else {
if ($channel->off($this->user)) {
@ -619,7 +619,7 @@ class OnCommand extends Command
function execute($channel)
{
if ($other) {
if ($this->other) {
$channel->error($this->user, _("Command not yet implemented."));
} else {
if ($channel->on($this->user)) {

104
lib/imchannel.php Normal file
View File

@ -0,0 +1,104 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, 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); }
class IMChannel extends Channel
{
var $imPlugin;
function source()
{
return $imPlugin->transport;
}
function __construct($imPlugin)
{
$this->imPlugin = $imPlugin;
}
function on($user)
{
return $this->set_notify($user, 1);
}
function off($user)
{
return $this->set_notify($user, 0);
}
function output($user, $text)
{
$text = '['.common_config('site', 'name') . '] ' . $text;
$this->imPlugin->send_message($this->imPlugin->get_screenname($user), $text);
}
function error($user, $text)
{
$text = '['.common_config('site', 'name') . '] ' . $text;
$screenname = $this->imPlugin->get_screenname($user);
if($screenname){
$this->imPlugin->send_message($screenname, $text);
return true;
}else{
common_log(LOG_ERR,
'Could not send error message to user ' . common_log_objstring($user) .
' on transport ' . $this->imPlugin->transport .' : user preference does not exist');
return false;
}
}
function set_notify($user, $notify)
{
$user_im_prefs = new User_im_prefs();
$user_im_prefs->transport = $this->imPlugin->transport;
$user_im_prefs->user_id = $user->id;
if($user_im_prefs->find() && $user_im_prefs->fetch()){
if($user_im_prefs->notify == $notify){
//notify is already set the way they want
return true;
}else{
$original = clone($user_im_prefs);
$user_im_prefs->notify = $notify;
$result = $user_im_prefs->update($original);
if (!$result) {
$last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
common_log(LOG_ERR,
'Could not set notify flag to ' . $notify .
' for user ' . common_log_objstring($user) .
' on transport ' . $this->imPlugin->transport .' : ' . $last_error->message);
return false;
} else {
common_log(LOG_INFO,
'User ' . $user->nickname . ' set notify flag to ' . $notify);
return true;
}
}
}else{
common_log(LOG_ERR,
'Could not set notify flag to ' . $notify .
' for user ' . common_log_objstring($user) .
' on transport ' . $this->imPlugin->transport .' : user preference does not exist');
return false;
}
}
}

70
lib/immanager.php Normal file
View File

@ -0,0 +1,70 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, 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); }
/**
* IKM background connection manager for IM-using queue handlers,
* allowing them to send outgoing messages on the right connection.
*
* In a multi-site queuedaemon.php run, one connection will be instantiated
* for each site being handled by the current process that has IM enabled.
*
* Implementations that extend this class will likely want to:
* 1) override start() with their connection process.
* 2) override handleInput() with what to do when data is waiting on
* one of the sockets
* 3) override idle($timeout) to do keepalives (if necessary)
* 4) implement send_raw_message() to send raw data that ImPlugin::enqueue_outgoing_raw
* enqueued
*/
abstract class ImManager extends IoManager
{
abstract function send_raw_message($data);
function __construct($imPlugin)
{
$this->plugin = $imPlugin;
//TODO We only really want to register this event if this is the thread that runs the ImManager
Event::addHandler('EndInitializeQueueManager', array($this, 'onEndInitializeQueueManager'));
}
/**
* Fetch the singleton manager for the current site.
* @return mixed ImManager, or false if unneeded
*/
public static function get()
{
throw new Exception('ImManager should be created using it\'s constructor, not the static get method');
}
/**
* Register notice queue handler
*
* @param QueueManager $manager
*
* @return boolean hook return
*/
function onEndInitializeQueueManager($manager)
{
$manager->connect($this->plugin->transport . '-out', new ImSenderQueueHandler($this->plugin, $this), 'imdaemon');
return true;
}
}

612
lib/implugin.php Normal file
View File

@ -0,0 +1,612 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Superclass for plugins that do instant messaging
*
* 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 Craig Andrews <candrews@integralblue.com>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/**
* Superclass for plugins that do authentication
*
* Implementations will likely want to override onStartIoManagerClasses() so that their
* IO manager is used
*
* @category Plugin
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
abstract class ImPlugin extends Plugin
{
//name of this IM transport
public $transport = null;
//list of screennames that should get all public notices
public $public = array();
/**
* normalize a screenname for comparison
*
* @param string $screenname screenname to normalize
*
* @return string an equivalent screenname in normalized form
*/
abstract function normalize($screenname);
/**
* validate (ensure the validity of) a screenname
*
* @param string $screenname screenname to validate
*
* @return boolean
*/
abstract function validate($screenname);
/**
* get the internationalized/translated display name of this IM service
*
* @return string
*/
abstract function getDisplayName();
/**
* send a single notice to a given screenname
* The implementation should put raw data, ready to send, into the outgoing
* queue using enqueue_outgoing_raw()
*
* @param string $screenname screenname to send to
* @param Notice $notice notice to send
*
* @return boolean success value
*/
function send_notice($screenname, $notice)
{
return $this->send_message($screenname, $this->format_notice($notice));
}
/**
* send a message (text) to a given screenname
* The implementation should put raw data, ready to send, into the outgoing
* queue using enqueue_outgoing_raw()
*
* @param string $screenname screenname to send to
* @param Notice $body text to send
*
* @return boolean success value
*/
abstract function send_message($screenname, $body);
/**
* receive a raw message
* Raw IM data is taken from the incoming queue, and passed to this function.
* It should parse the raw message and call handle_incoming()
*
* @param object $data raw IM data
*
* @return boolean success value
*/
abstract function receive_raw_message($data);
/**
* get the screenname of the daemon that sends and receives message for this service
*
* @return string screenname of this plugin
*/
abstract function daemon_screenname();
/**
* get the microid uri of a given screenname
*
* @param string $screenname screenname
*
* @return string microid uri
*/
function microiduri($screenname)
{
return $this->transport . ':' . $screenname;
}
//========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - MISC ========================\
/**
* Put raw message data (ready to send) into the outgoing queue
*
* @param object $data
*/
function enqueue_outgoing_raw($data)
{
$qm = QueueManager::get();
$qm->enqueue($data, $this->transport . '-out');
}
/**
* Put raw message data (received, ready to be processed) into the incoming queue
*
* @param object $data
*/
function enqueue_incoming_raw($data)
{
$qm = QueueManager::get();
$qm->enqueue($data, $this->transport . '-in');
}
/**
* given a screenname, get the corresponding user
*
* @param string $screenname
*
* @return User user
*/
function get_user($screenname)
{
$user_im_prefs = $this->get_user_im_prefs_from_screenname($screenname);
if($user_im_prefs){
$user = User::staticGet('id', $user_im_prefs->user_id);
$user_im_prefs->free();
return $user;
}else{
return false;
}
}
/**
* given a screenname, get the User_im_prefs object for this transport
*
* @param string $screenname
*
* @return User_im_prefs user_im_prefs
*/
function get_user_im_prefs_from_screenname($screenname)
{
if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'screenname' => $screenname) )){
return $user_im_prefs;
}else{
return false;
}
}
/**
* given a User, get their screenname
*
* @param User $user
*
* @return string screenname of that user
*/
function get_screenname($user)
{
$user_im_prefs = $this->get_user_im_prefs_from_user($user);
if($user_im_prefs){
return $user_im_prefs->screenname;
}else{
return false;
}
}
/**
* given a User, get their User_im_prefs
*
* @param User $user
*
* @return User_im_prefs user_im_prefs of that user
*/
function get_user_im_prefs_from_user($user)
{
if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'user_id' => $user->id) )){
return $user_im_prefs;
}else{
return false;
}
}
//========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - SENDING ========================\
/**
* Send a message to a given screenname from the site
*
* @param string $screenname screenname to send the message to
* @param string $msg message contents to send
*
* @param boolean success
*/
protected function send_from_site($screenname, $msg)
{
$text = '['.common_config('site', 'name') . '] ' . $msg;
$this->send_message($screenname, $text);
}
/**
* send a confirmation code to a user
*
* @param string $screenname screenname sending to
* @param string $code the confirmation code
* @param User $user user sending to
*
* @return boolean success value
*/
function send_confirmation_code($screenname, $code, $user)
{
$body = sprintf(_('User "%s" on %s has said that your %s screenname belongs to them. ' .
'If that\'s true, you can confirm by clicking on this URL: ' .
'%s' .
' . (If you cannot click it, copy-and-paste it into the ' .
'address bar of your browser). If that user isn\'t you, ' .
'or if you didn\'t request this confirmation, just ignore this message.'),
$user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code)));
return $this->send_message($screenname, $body);
}
/**
* send a notice to all public listeners
*
* For notices that are generated on the local system (by users), we can optionally
* forward them to remote listeners by XMPP.
*
* @param Notice $notice notice to broadcast
*
* @return boolean success flag
*/
function public_notice($notice)
{
// Now, users who want everything
// FIXME PRIV don't send out private messages here
// XXX: should we send out non-local messages if public,localonly
// = false? I think not
foreach ($this->public as $screenname) {
common_log(LOG_INFO,
'Sending notice ' . $notice->id .
' to public listener ' . $screenname,
__FILE__);
$this->send_notice($screenname, $notice);
}
return true;
}
/**
* broadcast a notice to all subscribers and reply recipients
*
* This function will send a notice to all subscribers on the local server
* who have IM addresses, and have IM notification enabled, and
* have this subscription enabled for IM. It also sends the notice to
* all recipients of @-replies who have IM addresses and IM notification
* enabled. This is really the heart of IM distribution in StatusNet.
*
* @param Notice $notice The notice to broadcast
*
* @return boolean success flag
*/
function broadcast_notice($notice)
{
$ni = $notice->whoGets();
foreach ($ni as $user_id => $reason) {
$user = User::staticGet($user_id);
if (empty($user)) {
// either not a local user, or just not found
continue;
}
$user_im_prefs = $this->get_user_im_prefs_from_user($user);
if(!$user_im_prefs || !$user_im_prefs->notify){
continue;
}
switch ($reason) {
case NOTICE_INBOX_SOURCE_REPLY:
if (!$user_im_prefs->replies) {
continue 2;
}
break;
case NOTICE_INBOX_SOURCE_SUB:
$sub = Subscription::pkeyGet(array('subscriber' => $user->id,
'subscribed' => $notice->profile_id));
if (empty($sub) || !$sub->jabber) {
continue 2;
}
break;
case NOTICE_INBOX_SOURCE_GROUP:
break;
default:
throw new Exception(sprintf(_("Unknown inbox source %d."), $reason));
}
common_log(LOG_INFO,
'Sending notice ' . $notice->id . ' to ' . $user_im_prefs->screenname,
__FILE__);
$this->send_notice($user_im_prefs->screenname, $notice);
$user_im_prefs->free();
}
return true;
}
/**
* makes a plain-text formatted version of a notice, suitable for IM distribution
*
* @param Notice $notice notice being sent
*
* @return string plain-text version of the notice, with user nickname prefixed
*/
function format_notice($notice)
{
$profile = $notice->getProfile();
return $profile->nickname . ': ' . $notice->content . ' [' . $notice->id . ']';
}
//========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - RECEIVING ========================\
/**
* Attempt to handle a message as a command
* @param User $user user the message is from
* @param string $body message text
* @return boolean true if the message was a command and was executed, false if it was not a command
*/
protected function handle_command($user, $body)
{
$inter = new CommandInterpreter();
$cmd = $inter->handle_command($user, $body);
if ($cmd) {
$chan = new IMChannel($this);
$cmd->execute($chan);
return true;
} else {
return false;
}
}
/**
* Is some text an autoreply message?
* @param string $txt message text
* @return boolean true if autoreply
*/
protected function is_autoreply($txt)
{
if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) {
return true;
} else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) {
return true;
} else {
return false;
}
}
/**
* Is some text an OTR message?
* @param string $txt message text
* @return boolean true if OTR
*/
protected function is_otr($txt)
{
if (preg_match('/^\?OTR/', $txt)) {
return true;
} else {
return false;
}
}
/**
* Helper for handling incoming messages
* Your incoming message handler will probably want to call this function
*
* @param string $from screenname the message was sent from
* @param string $message message contents
*
* @param boolean success
*/
protected function handle_incoming($from, $notice_text)
{
$user = $this->get_user($from);
// For common_current_user to work
global $_cur;
$_cur = $user;
if (!$user) {
$this->send_from_site($from, 'Unknown user; go to ' .
common_local_url('imsettings') .
' to add your address to your account');
common_log(LOG_WARNING, 'Message from unknown user ' . $from);
return;
}
if ($this->handle_command($user, $notice_text)) {
common_log(LOG_INFO, "Command message by $from handled.");
return;
} else if ($this->is_autoreply($notice_text)) {
common_log(LOG_INFO, 'Ignoring auto reply from ' . $from);
return;
} else if ($this->is_otr($notice_text)) {
common_log(LOG_INFO, 'Ignoring OTR from ' . $from);
return;
} else {
common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
$this->add_notice($from, $user, $notice_text);
}
$user->free();
unset($user);
unset($_cur);
unset($message);
}
/**
* Helper for handling incoming messages
* Your incoming message handler will probably want to call this function
*
* @param string $from screenname the message was sent from
* @param string $message message contents
*
* @param boolean success
*/
protected function add_notice($screenname, $user, $body)
{
$body = trim(strip_tags($body));
$content_shortened = common_shorten_links($body);
if (Notice::contentTooLong($content_shortened)) {
$this->send_from_site($screenname, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'),
Notice::maxContent(),
mb_strlen($content_shortened)));
return;
}
try {
$notice = Notice::saveNew($user->id, $content_shortened, $this->transport);
} catch (Exception $e) {
common_log(LOG_ERR, $e->getMessage());
$this->send_from_site($from, $e->getMessage());
return;
}
common_broadcast_notice($notice);
common_log(LOG_INFO,
'Added notice ' . $notice->id . ' from user ' . $user->nickname);
$notice->free();
unset($notice);
}
//========================EVENT HANDLERS========================\
/**
* Register notice queue handler
*
* @param QueueManager $manager
*
* @return boolean hook return
*/
function onEndInitializeQueueManager($manager)
{
$manager->connect($this->transport . '-in', new ImReceiverQueueHandler($this));
$manager->connect($this->transport, new ImQueueHandler($this));
return true;
}
function onStartImDaemonIoManagers(&$classes)
{
//$classes[] = new ImManager($this); // handles sending/receiving/pings/reconnects
return true;
}
function onStartEnqueueNotice($notice, &$transports)
{
$profile = Profile::staticGet($notice->profile_id);
if (!$profile) {
common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
'unknown profile ' . common_log_objstring($notice),
__FILE__);
}else{
$transports[] = $this->transport;
}
return true;
}
function onEndShowHeadElements($action)
{
$aname = $action->trimmed('action');
if ($aname == 'shownotice') {
$user_im_prefs = new User_im_prefs();
$user_im_prefs->user_id = $action->profile->id;
$user_im_prefs->transport = $this->transport;
if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->notice->uri) {
$id = new Microid($this->microiduri($user_im_prefs->screenname),
$action->notice->uri);
$action->element('meta', array('name' => 'microid',
'content' => $id->toString()));
}
} else if ($aname == 'showstream') {
$user_im_prefs = new User_im_prefs();
$user_im_prefs->user_id = $action->user->id;
$user_im_prefs->transport = $this->transport;
if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->profile->profileurl) {
$id = new Microid($this->microiduri($user_im_prefs->screenname),
$action->selfUrl());
$action->element('meta', array('name' => 'microid',
'content' => $id->toString()));
}
}
}
function onNormalizeImScreenname($transport, &$screenname)
{
if($transport == $this->transport)
{
$screenname = $this->normalize($screenname);
return false;
}
}
function onValidateImScreenname($transport, $screenname, &$valid)
{
if($transport == $this->transport)
{
$valid = $this->validate($screenname);
return false;
}
}
function onGetImTransports(&$transports)
{
$transports[$this->transport] = array('display' => $this->getDisplayName());
}
function onSendImConfirmationCode($transport, $screenname, $code, $user)
{
if($transport == $this->transport)
{
$this->send_confirmation_code($screenname, $code, $user);
return false;
}
}
function onUserDeleteRelated($user, &$tables)
{
$tables[] = 'User_im_prefs';
return true;
}
function initialize()
{
if(is_null($this->transport)){
throw new Exception('transport cannot be null');
}
}
}

View File

@ -17,31 +17,32 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
/**
* Queue handler for pushing new notices to Jabber users.
* @fixme this exception handling doesn't look very good.
* Common superclass for all IM sending queue handlers.
*/
class JabberQueueHandler extends QueueHandler
{
var $conn = null;
function transport()
class ImQueueHandler extends QueueHandler
{
function __construct($plugin)
{
return 'jabber';
$this->plugin = $plugin;
}
/**
* Handle a notice
* @param Notice $notice
* @return boolean success
*/
function handle($notice)
{
require_once(INSTALLDIR.'/lib/jabber.php');
try {
return jabber_broadcast_notice($notice);
} catch (XMPPHP_Exception $e) {
$this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
return false;
$this->plugin->broadcast_notice($notice);
if ($notice->is_local == Notice::LOCAL_PUBLIC ||
$notice->is_local == Notice::LOCAL_NONPUBLIC) {
$this->plugin->public_notice($notice);
}
return true;
}
}

View File

@ -17,29 +17,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
/**
* Queue handler for pushing new notices to public XMPP subscribers.
* Common superclass for all IM receiving queue handlers.
*/
class PublicQueueHandler extends QueueHandler
{
function transport()
class ImReceiverQueueHandler extends QueueHandler
{
function __construct($plugin)
{
return 'public';
$this->plugin = $plugin;
}
function handle($notice)
/**
* Handle incoming IM data sent by a user to the IM bot
* @param object $data
* @return boolean success
*/
function handle($data)
{
require_once(INSTALLDIR.'/lib/jabber.php');
try {
return jabber_public_notice($notice);
} catch (XMPPHP_Exception $e) {
$this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
return false;
}
return $this->plugin->receive_raw_message($data);
}
}

View File

@ -0,0 +1,44 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, 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); }
/**
* Common superclass for all IM sending queue handlers.
*/
class ImSenderQueueHandler extends QueueHandler
{
function __construct($plugin, $immanager)
{
$this->plugin = $plugin;
$this->immanager = $immanager;
}
/**
* Handle outgoing IM data to be sent from the bot to a user
* @param object $data
* @return boolean success
*/
function handle($data)
{
return $this->immanager->send_raw_message($data);
}
}

View File

@ -1,473 +0,0 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* utility functions for Jabber/GTalk/XMPP messages
*
* 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 Network
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2008 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once 'XMPPHP/XMPP.php';
/**
* checks whether a string is a syntactically valid Jabber ID (JID)
*
* @param string $jid string to check
*
* @return boolean whether the string is a valid JID
*/
function jabber_valid_base_jid($jid)
{
// Cheap but effective
return Validate::email($jid);
}
/**
* normalizes a Jabber ID for comparison
*
* @param string $jid JID to check
*
* @return string an equivalent JID in normalized (lowercase) form
*/
function jabber_normalize_jid($jid)
{
if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $jid, $matches)) {
$node = $matches[1];
$server = $matches[2];
return strtolower($node.'@'.$server);
} else {
return null;
}
}
/**
* the JID of the Jabber daemon for this StatusNet instance
*
* @return string JID of the Jabber daemon
*/
function jabber_daemon_address()
{
return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server');
}
class Sharing_XMPP extends XMPPHP_XMPP
{
function getSocket()
{
return $this->socket;
}
}
/**
* Build an XMPP proxy connection that'll save outgoing messages
* to the 'xmppout' queue to be picked up by xmppdaemon later.
*/
function jabber_proxy()
{
$proxy = new Queued_XMPP(common_config('xmpp', 'host') ?
common_config('xmpp', 'host') :
common_config('xmpp', 'server'),
common_config('xmpp', 'port'),
common_config('xmpp', 'user'),
common_config('xmpp', 'password'),
common_config('xmpp', 'resource') . 'daemon',
common_config('xmpp', 'server'),
common_config('xmpp', 'debug') ?
true : false,
common_config('xmpp', 'debug') ?
XMPPHP_Log::LEVEL_VERBOSE : null);
return $proxy;
}
/**
* Lazy-connect the configured Jabber account to the configured server;
* if already opened, the same connection will be returned.
*
* In a multi-site background process, each site configuration
* will get its own connection.
*
* @param string $resource Resource to connect (defaults to configured resource)
*
* @return XMPPHP connection to the configured server
*/
function jabber_connect($resource=null)
{
static $connections = array();
$site = common_config('site', 'server');
if (empty($connections[$site])) {
if (empty($resource)) {
$resource = common_config('xmpp', 'resource');
}
$conn = new Sharing_XMPP(common_config('xmpp', 'host') ?
common_config('xmpp', 'host') :
common_config('xmpp', 'server'),
common_config('xmpp', 'port'),
common_config('xmpp', 'user'),
common_config('xmpp', 'password'),
$resource,
common_config('xmpp', 'server'),
common_config('xmpp', 'debug') ?
true : false,
common_config('xmpp', 'debug') ?
XMPPHP_Log::LEVEL_VERBOSE : null
);
if (!$conn) {
return false;
}
$connections[$site] = $conn;
$conn->autoSubscribe();
$conn->useEncryption(common_config('xmpp', 'encryption'));
try {
common_log(LOG_INFO, __METHOD__ . ": connecting " .
common_config('xmpp', 'user') . '/' . $resource);
//$conn->connect(true); // true = persistent connection
$conn->connect(); // persistent connections break multisite
} catch (XMPPHP_Exception $e) {
common_log(LOG_ERR, $e->getMessage());
return false;
}
$conn->processUntil('session_start');
}
return $connections[$site];
}
/**
* Queue send for a single notice to a given Jabber address
*
* @param string $to JID to send the notice to
* @param Notice $notice notice to send
*
* @return boolean success value
*/
function jabber_send_notice($to, $notice)
{
$conn = jabber_proxy();
$profile = Profile::staticGet($notice->profile_id);
if (!$profile) {
common_log(LOG_WARNING, 'Refusing to send notice with ' .
'unknown profile ' . common_log_objstring($notice),
__FILE__);
return false;
}
$msg = jabber_format_notice($profile, $notice);
$entry = jabber_format_entry($profile, $notice);
$conn->message($to, $msg, 'chat', null, $entry);
$profile->free();
return true;
}
/**
* extra information for XMPP messages, as defined by Twitter
*
* @param Profile $profile Profile of the sending user
* @param Notice $notice Notice being sent
*
* @return string Extra information (Atom, HTML, addresses) in string format
*/
function jabber_format_entry($profile, $notice)
{
$entry = $notice->asAtomEntry(true, true);
$xs = new XMLStringer();
$xs->elementStart('html', array('xmlns' => 'http://jabber.org/protocol/xhtml-im'));
$xs->elementStart('body', array('xmlns' => 'http://www.w3.org/1999/xhtml'));
$xs->element('a', array('href' => $profile->profileurl),
$profile->nickname);
$xs->text(": ");
if (!empty($notice->rendered)) {
$xs->raw($notice->rendered);
} else {
$xs->raw(common_render_content($notice->content, $notice));
}
$xs->text(" ");
$xs->element('a', array(
'href'=>common_local_url('conversation',
array('id' => $notice->conversation)).'#notice-'.$notice->id
),sprintf(_('[%s]'),$notice->id));
$xs->elementEnd('body');
$xs->elementEnd('html');
$html = $xs->getString();
return $html . ' ' . $entry;
}
/**
* sends a single text message to a given JID
*
* @param string $to JID to send the message to
* @param string $body body of the message
* @param string $type type of the message
* @param string $subject subject of the message
*
* @return boolean success flag
*/
function jabber_send_message($to, $body, $type='chat', $subject=null)
{
$conn = jabber_proxy();
$conn->message($to, $body, $type, $subject);
return true;
}
/**
* sends a presence stanza on the Jabber network
*
* @param string $status current status, free-form string
* @param string $show structured status value
* @param string $to recipient of presence, null for general
* @param string $type type of status message, related to $show
* @param int $priority priority of the presence
*
* @return boolean success value
*/
function jabber_send_presence($status, $show='available', $to=null,
$type = 'available', $priority=null)
{
$conn = jabber_connect();
if (!$conn) {
return false;
}
$conn->presence($status, $show, $to, $type, $priority);
return true;
}
/**
* sends a confirmation request to a JID
*
* @param string $code confirmation code for confirmation URL
* @param string $nickname nickname of confirming user
* @param string $address JID to send confirmation to
*
* @return boolean success flag
*/
function jabber_confirm_address($code, $nickname, $address)
{
$body = 'User "' . $nickname . '" on ' . common_config('site', 'name') . ' ' .
'has said that your Jabber ID belongs to them. ' .
'If that\'s true, you can confirm by clicking on this URL: ' .
common_local_url('confirmaddress', array('code' => $code)) .
' . (If you cannot click it, copy-and-paste it into the ' .
'address bar of your browser). If that user isn\'t you, ' .
'or if you didn\'t request this confirmation, just ignore this message.';
return jabber_send_message($address, $body);
}
/**
* sends a "special" presence stanza on the Jabber network
*
* @param string $type Type of presence
* @param string $to JID to send presence to
* @param string $show show value for presence
* @param string $status status value for presence
*
* @return boolean success flag
*
* @see jabber_send_presence()
*/
function jabber_special_presence($type, $to=null, $show=null, $status=null)
{
// FIXME: why use this instead of jabber_send_presence()?
$conn = jabber_connect();
$to = htmlspecialchars($to);
$status = htmlspecialchars($status);
$out = "<presence";
if ($to) {
$out .= " to='$to'";
}
if ($type) {
$out .= " type='$type'";
}
if ($show == 'available' and !$status) {
$out .= "/>";
} else {
$out .= ">";
if ($show && ($show != 'available')) {
$out .= "<show>$show</show>";
}
if ($status) {
$out .= "<status>$status</status>";
}
$out .= "</presence>";
}
$conn->send($out);
}
/**
* Queue broadcast of a notice to all subscribers and reply recipients
*
* This function will send a notice to all subscribers on the local server
* who have Jabber addresses, and have Jabber notification enabled, and
* have this subscription enabled for Jabber. It also sends the notice to
* all recipients of @-replies who have Jabber addresses and Jabber notification
* enabled. This is really the heart of Jabber distribution in StatusNet.
*
* @param Notice $notice The notice to broadcast
*
* @return boolean success flag
*/
function jabber_broadcast_notice($notice)
{
if (!common_config('xmpp', 'enabled')) {
return true;
}
$profile = Profile::staticGet($notice->profile_id);
if (!$profile) {
common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
'unknown profile ' . common_log_objstring($notice),
__FILE__);
return false;
}
$msg = jabber_format_notice($profile, $notice);
$entry = jabber_format_entry($profile, $notice);
$profile->free();
unset($profile);
$sent_to = array();
$conn = jabber_proxy();
$ni = $notice->whoGets();
foreach ($ni as $user_id => $reason) {
$user = User::staticGet($user_id);
if (empty($user) ||
empty($user->jabber) ||
!$user->jabbernotify) {
// either not a local user, or just not found
continue;
}
switch ($reason) {
case NOTICE_INBOX_SOURCE_REPLY:
if (!$user->jabberreplies) {
continue 2;
}
break;
case NOTICE_INBOX_SOURCE_SUB:
$sub = Subscription::pkeyGet(array('subscriber' => $user->id,
'subscribed' => $notice->profile_id));
if (empty($sub) || !$sub->jabber) {
continue 2;
}
break;
case NOTICE_INBOX_SOURCE_GROUP:
break;
default:
throw new Exception(sprintf(_("Unknown inbox source %d."), $reason));
}
common_log(LOG_INFO,
'Sending notice ' . $notice->id . ' to ' . $user->jabber,
__FILE__);
$conn->message($user->jabber, $msg, 'chat', null, $entry);
}
return true;
}
/**
* Queue send of a notice to all public listeners
*
* For notices that are generated on the local system (by users), we can optionally
* forward them to remote listeners by XMPP.
*
* @param Notice $notice notice to broadcast
*
* @return boolean success flag
*/
function jabber_public_notice($notice)
{
// Now, users who want everything
$public = common_config('xmpp', 'public');
// FIXME PRIV don't send out private messages here
// XXX: should we send out non-local messages if public,localonly
// = false? I think not
if ($public && $notice->is_local == Notice::LOCAL_PUBLIC) {
$profile = Profile::staticGet($notice->profile_id);
if (!$profile) {
common_log(LOG_WARNING, 'Refusing to broadcast notice with ' .
'unknown profile ' . common_log_objstring($notice),
__FILE__);
return false;
}
$msg = jabber_format_notice($profile, $notice);
$entry = jabber_format_entry($profile, $notice);
$conn = jabber_proxy();
foreach ($public as $address) {
common_log(LOG_INFO,
'Sending notice ' . $notice->id .
' to public listener ' . $address,
__FILE__);
$conn->message($address, $msg, 'chat', null, $entry);
}
$profile->free();
}
return true;
}
/**
* makes a plain-text formatted version of a notice, suitable for Jabber distribution
*
* @param Profile &$profile profile of the sending user
* @param Notice &$notice notice being sent
*
* @return string plain-text version of the notice, with user nickname prefixed
*/
function jabber_format_notice(&$profile, &$notice)
{
return $profile->nickname . ': ' . $notice->content . ' [' . $notice->id . ']';
}

View File

@ -36,20 +36,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
class QueueHandler
{
/**
* Return transport keyword which identifies items this queue handler
* services; must be defined for all subclasses.
*
* Must be 8 characters or less to fit in the queue_item database.
* ex "email", "jabber", "sms", "irc", ...
*
* @return string
*/
function transport()
{
return null;
}
/**
* Here's the meat of your queue handler -- you're handed a Notice
* or other object, which you may do as you will with.

View File

@ -213,18 +213,8 @@ abstract class QueueManager extends IoManager
$this->connect('sms', 'SmsQueueHandler');
}
// XMPP output handlers...
$this->connect('jabber', 'JabberQueueHandler');
$this->connect('public', 'PublicQueueHandler');
// @fixme this should get an actual queue
//$this->connect('confirm', 'XmppConfirmHandler');
// For compat with old plugins not registering their own handlers.
$this->connect('plugin', 'PluginQueueHandler');
$this->connect('xmppout', 'XmppOutQueueHandler', 'xmppdaemon');
}
Event::handle('EndInitializeQueueManager', array($this));
}
@ -251,8 +241,8 @@ abstract class QueueManager extends IoManager
$group = 'queuedaemon';
if ($this->master) {
// hack hack
if ($this->master instanceof XmppMaster) {
return 'xmppdaemon';
if ($this->master instanceof ImMaster) {
return 'imdaemon';
}
}
return $group;

View File

@ -36,7 +36,7 @@ class QueueMonitor
* Only explicitly listed thread/site/queue owners will be incremented.
*
* @param string $key counter name
* @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01'
* @param array $owners list of owner keys like 'queue:xmpp' or 'site:stat01'
*/
public function stats($key, $owners=array())
{

View File

@ -994,18 +994,9 @@ function common_enqueue_notice($notice)
$transports = $allTransports;
$xmpp = common_config('xmpp', 'enabled');
if ($xmpp) {
$transports[] = 'jabber';
}
if ($notice->is_local == Notice::LOCAL_PUBLIC ||
$notice->is_local == Notice::LOCAL_NONPUBLIC) {
$transports = array_merge($transports, $localTransports);
if ($xmpp) {
$transports[] = 'public';
}
}
if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) {

View File

@ -1,485 +0,0 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, 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); }
/**
* XMPP background connection manager for XMPP-using queue handlers,
* allowing them to send outgoing messages on the right connection.
*
* Input is handled during socket select loop, keepalive pings during idle.
* Any incoming messages will be forwarded to the main XmppDaemon process,
* which handles direct user interaction.
*
* In a multi-site queuedaemon.php run, one connection will be instantiated
* for each site being handled by the current process that has XMPP enabled.
*/
class XmppManager extends IoManager
{
protected $site = null;
protected $pingid = 0;
protected $lastping = null;
static protected $singletons = array();
const PING_INTERVAL = 120;
/**
* Fetch the singleton XmppManager for the current site.
* @return mixed XmppManager, or false if unneeded
*/
public static function get()
{
if (common_config('xmpp', 'enabled')) {
$site = common_config('site', 'server');
if (empty(self::$singletons[$site])) {
self::$singletons[$site] = new XmppManager();
}
return self::$singletons[$site];
} else {
return false;
}
}
/**
* Tell the i/o master we need one instance for each supporting site
* being handled in this process.
*/
public static function multiSite()
{
return IoManager::INSTANCE_PER_SITE;
}
function __construct()
{
$this->site = common_config('site', 'server');
$this->resource = common_config('xmpp', 'resource') . 'daemon';
}
/**
* Initialize connection to server.
* @return boolean true on success
*/
public function start($master)
{
parent::start($master);
$this->switchSite();
require_once INSTALLDIR . "/lib/jabber.php";
# Low priority; we don't want to receive messages
common_log(LOG_INFO, "INITIALIZE");
$this->conn = jabber_connect($this->resource);
if (empty($this->conn)) {
common_log(LOG_ERR, "Couldn't connect to server.");
return false;
}
$this->log(LOG_DEBUG, "Initializing stanza handlers.");
$this->conn->addEventHandler('message', 'handle_message', $this);
$this->conn->addEventHandler('presence', 'handle_presence', $this);
$this->conn->addEventHandler('reconnect', 'handle_reconnect', $this);
$this->conn->setReconnectTimeout(600);
jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', 100);
return !is_null($this->conn);
}
/**
* Message pump is triggered on socket input, so we only need an idle()
* call often enough to trigger our outgoing pings.
*/
function timeout()
{
return self::PING_INTERVAL;
}
/**
* Lists the XMPP connection socket to allow i/o master to wake
* when input comes in here as well as from the queue source.
*
* @return array of resources
*/
public function getSockets()
{
if ($this->conn) {
return array($this->conn->getSocket());
} else {
return array();
}
}
/**
* Process XMPP events that have come in over the wire.
* Side effects: may switch site configuration
* @fixme may kill process on XMPP error
* @param resource $socket
*/
public function handleInput($socket)
{
$this->switchSite();
# Process the queue for as long as needed
try {
if ($this->conn) {
assert($socket === $this->conn->getSocket());
common_log(LOG_DEBUG, "Servicing the XMPP queue.");
$this->stats('xmpp_process');
$this->conn->processTime(0);
}
} catch (XMPPHP_Exception $e) {
common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
die($e->getMessage());
}
}
/**
* Idle processing for io manager's execution loop.
* Send keepalive pings to server.
*
* Side effect: kills process on exception from XMPP library.
*
* @fixme non-dying error handling
*/
public function idle($timeout=0)
{
if ($this->conn) {
$now = time();
if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) {
$this->switchSite();
try {
$this->sendPing();
$this->lastping = $now;
} catch (XMPPHP_Exception $e) {
common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
die($e->getMessage());
}
}
}
}
/**
* For queue handlers to pass us a message to push out,
* if we're active.
*
* @fixme should this be blocking etc?
*
* @param string $msg XML stanza to send
* @return boolean success
*/
public function send($msg)
{
if ($this->conn && !$this->conn->isDisconnected()) {
$bytes = $this->conn->send($msg);
if ($bytes > 0) {
$this->conn->processTime(0);
return true;
} else {
return false;
}
} else {
// Can't send right now...
return false;
}
}
/**
* Send a keepalive ping to the XMPP server.
*/
protected function sendPing()
{
$jid = jabber_daemon_address().'/'.$this->resource;
$server = common_config('xmpp', 'server');
if (!isset($this->pingid)) {
$this->pingid = 0;
} else {
$this->pingid++;
}
common_log(LOG_DEBUG, "Sending ping #{$this->pingid}");
$this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
}
/**
* Callback for Jabber reconnect event
* @param $pl
*/
function handle_reconnect(&$pl)
{
common_log(LOG_NOTICE, 'XMPP reconnected');
$this->conn->processUntil('session_start');
$this->conn->presence(null, 'available', null, 'available', 100);
}
function get_user($from)
{
$user = User::staticGet('jabber', jabber_normalize_jid($from));
return $user;
}
/**
* XMPP callback for handling message input...
* @param array $pl XMPP payload
*/
function handle_message(&$pl)
{
$from = jabber_normalize_jid($pl['from']);
if ($pl['type'] != 'chat') {
$this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from.");
return;
}
if (mb_strlen($pl['body']) == 0) {
$this->log(LOG_WARNING, "Ignoring message with empty body from $from.");
return;
}
// Forwarded from another daemon for us to handle; this shouldn't
// happen any more but we might get some legacy items.
if ($this->is_self($from)) {
$this->log(LOG_INFO, "Got forwarded notice from self ($from).");
$from = $this->get_ofrom($pl);
$this->log(LOG_INFO, "Originally sent by $from.");
if (is_null($from) || $this->is_self($from)) {
$this->log(LOG_INFO, "Ignoring notice originally sent by $from.");
return;
}
}
$user = $this->get_user($from);
// For common_current_user to work
global $_cur;
$_cur = $user;
if (!$user) {
$this->from_site($from, 'Unknown user; go to ' .
common_local_url('imsettings') .
' to add your address to your account');
$this->log(LOG_WARNING, 'Message from unknown user ' . $from);
return;
}
if ($this->handle_command($user, $pl['body'])) {
$this->log(LOG_INFO, "Command message by $from handled.");
return;
} else if ($this->is_autoreply($pl['body'])) {
$this->log(LOG_INFO, 'Ignoring auto reply from ' . $from);
return;
} else if ($this->is_otr($pl['body'])) {
$this->log(LOG_INFO, 'Ignoring OTR from ' . $from);
return;
} else {
$this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
$this->add_notice($user, $pl);
}
$user->free();
unset($user);
unset($_cur);
unset($pl['xml']);
$pl['xml'] = null;
$pl = null;
unset($pl);
}
function is_self($from)
{
return preg_match('/^'.strtolower(jabber_daemon_address()).'/', strtolower($from));
}
function get_ofrom($pl)
{
$xml = $pl['xml'];
$addresses = $xml->sub('addresses');
if (!$addresses) {
$this->log(LOG_WARNING, 'Forwarded message without addresses');
return null;
}
$address = $addresses->sub('address');
if (!$address) {
$this->log(LOG_WARNING, 'Forwarded message without address');
return null;
}
if (!array_key_exists('type', $address->attrs)) {
$this->log(LOG_WARNING, 'No type for forwarded message');
return null;
}
$type = $address->attrs['type'];
if ($type != 'ofrom') {
$this->log(LOG_WARNING, 'Type of forwarded message is not ofrom');
return null;
}
if (!array_key_exists('jid', $address->attrs)) {
$this->log(LOG_WARNING, 'No jid for forwarded message');
return null;
}
$jid = $address->attrs['jid'];
if (!$jid) {
$this->log(LOG_WARNING, 'Could not get jid from address');
return null;
}
$this->log(LOG_DEBUG, 'Got message forwarded from jid ' . $jid);
return $jid;
}
function is_autoreply($txt)
{
if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) {
return true;
} else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) {
return true;
} else {
return false;
}
}
function is_otr($txt)
{
if (preg_match('/^\?OTR/', $txt)) {
return true;
} else {
return false;
}
}
function from_site($address, $msg)
{
$text = '['.common_config('site', 'name') . '] ' . $msg;
jabber_send_message($address, $text);
}
function handle_command($user, $body)
{
$inter = new CommandInterpreter();
$cmd = $inter->handle_command($user, $body);
if ($cmd) {
$chan = new XMPPChannel($this->conn);
$cmd->execute($chan);
return true;
} else {
return false;
}
}
function add_notice(&$user, &$pl)
{
$body = trim($pl['body']);
$content_shortened = common_shorten_links($body);
if (Notice::contentTooLong($content_shortened)) {
$from = jabber_normalize_jid($pl['from']);
$this->from_site($from, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'),
Notice::maxContent(),
mb_strlen($content_shortened)));
return;
}
try {
$notice = Notice::saveNew($user->id, $content_shortened, 'xmpp');
} catch (Exception $e) {
$this->log(LOG_ERR, $e->getMessage());
$this->from_site($user->jabber, $e->getMessage());
return;
}
common_broadcast_notice($notice);
$this->log(LOG_INFO,
'Added notice ' . $notice->id . ' from user ' . $user->nickname);
$notice->free();
unset($notice);
}
function handle_presence(&$pl)
{
$from = jabber_normalize_jid($pl['from']);
switch ($pl['type']) {
case 'subscribe':
# We let anyone subscribe
$this->subscribed($from);
$this->log(LOG_INFO,
'Accepted subscription from ' . $from);
break;
case 'subscribed':
case 'unsubscribed':
case 'unsubscribe':
$this->log(LOG_INFO,
'Ignoring "' . $pl['type'] . '" from ' . $from);
break;
default:
if (!$pl['type']) {
$user = User::staticGet('jabber', $from);
if (!$user) {
$this->log(LOG_WARNING, 'Presence from unknown user ' . $from);
return;
}
if ($user->updatefrompresence) {
$this->log(LOG_INFO, 'Updating ' . $user->nickname .
' status from presence.');
$this->add_notice($user, $pl);
}
$user->free();
unset($user);
}
break;
}
unset($pl['xml']);
$pl['xml'] = null;
$pl = null;
unset($pl);
}
function log($level, $msg)
{
$text = 'XMPPDaemon('.$this->resource.'): '.$msg;
common_log($level, $text);
}
function subscribed($to)
{
jabber_special_presence('subscribed', $to);
}
/**
* Make sure we're on the right site configuration
*/
protected function switchSite()
{
if ($this->site != common_config('site', 'server')) {
common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site");
$this->stats('switch');
StatusNet::init($this->site);
}
}
}

View File

@ -1,55 +0,0 @@
<?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/>.
*/
/**
* Queue handler for pre-processed outgoing XMPP messages.
* Formatted XML stanzas will have been pushed into the queue
* via the Queued_XMPP connection proxy, probably from some
* other queue processor.
*
* Here, the XML stanzas are simply pulled out of the queue and
* pushed out over the wire; an XmppManager is needed to set up
* and maintain the actual server connection.
*
* This queue will be run via XmppDaemon rather than QueueDaemon.
*
* @author Brion Vibber <brion@status.net>
*/
class XmppOutQueueHandler extends QueueHandler
{
function transport() {
return 'xmppout';
}
/**
* Take a previously-queued XMPP stanza and send it out ot the server.
* @param string $msg
* @return boolean true on success
*/
function handle($msg)
{
assert(is_string($msg));
$xmpp = XmppManager::get();
$ok = $xmpp->send($msg);
return $ok;
}
}

162
plugins/Aim/AimPlugin.php Normal file
View File

@ -0,0 +1,162 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
* Send and receive notices using the AIM network
*
* PHP version 5
*
* 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 IM
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
// We bundle the phptoclib library...
set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phptoclib');
/**
* Plugin for AIM
*
* @category Plugin
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class AimPlugin extends ImPlugin
{
public $user = null;
public $password = null;
public $publicFeed = array();
public $transport = 'aim';
function getDisplayName()
{
return _m('AIM');
}
function normalize($screenname)
{
$screenname = str_replace(" ","", $screenname);
return strtolower($screenname);
}
function daemon_screenname()
{
return $this->user;
}
function validate($screenname)
{
if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) {
return true;
}else{
return false;
}
}
/**
* Load related modules when needed
*
* @param string $cls Name of the class to be loaded
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onAutoload($cls)
{
$dir = dirname(__FILE__);
switch ($cls)
{
case 'Aim':
require_once(INSTALLDIR.'/plugins/Aim/extlib/phptoclib/aimclassw.php');
return false;
case 'AimManager':
include_once $dir . '/'.strtolower($cls).'.php';
return false;
case 'Fake_Aim':
include_once $dir . '/'. $cls .'.php';
return false;
default:
return true;
}
}
function onStartImDaemonIoManagers(&$classes)
{
parent::onStartImDaemonIoManagers(&$classes);
$classes[] = new AimManager($this); // handles sending/receiving
return true;
}
function microiduri($screenname)
{
return 'aim:' . $screenname;
}
function send_message($screenname, $body)
{
$this->fake_aim->sendIm($screenname, $body);
$this->enqueue_outgoing_raw($this->fake_aim->would_be_sent);
return true;
}
function receive_raw_message($message)
{
$info=Aim::getMessageInfo($message);
$from = $info['from'];
$user = $this->get_user($from);
$notice_text = $info['message'];
return $this->handle_incoming($from, $notice_text);
}
function initialize(){
if(!isset($this->user)){
throw new Exception("must specify a user");
}
if(!isset($this->password)){
throw new Exception("must specify a password");
}
$this->fake_aim = new Fake_Aim($this->user,$this->password,4);
return true;
}
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'AIM',
'version' => STATUSNET_VERSION,
'author' => 'Craig Andrews',
'homepage' => 'http://status.net/wiki/Plugin:AIM',
'rawdescription' =>
_m('The AIM plugin allows users to send and receive notices over the AIM network.'));
return true;
}
}

43
plugins/Aim/Fake_Aim.php Normal file
View File

@ -0,0 +1,43 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Instead of sending AIM messages, retrieve the raw data that would be sent
*
* 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 Network
* @package StatusNet
* @author Craig Andrews <candrews@integralblue.com>
* @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/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
class Fake_Aim extends Aim
{
public $would_be_sent = null;
function sflapSend($sflap_type, $sflap_data, $no_null, $formatted)
{
$this->would_be_sent = array($sflap_type, $sflap_data, $no_null, $formatted);
}
}

27
plugins/Aim/README Normal file
View File

@ -0,0 +1,27 @@
The AIM plugin allows users to send and receive notices over the AIM network.
Installation
============
add "addPlugin('aim',
array('setting'=>'value', 'setting2'=>'value2', ...);"
to the bottom of your config.php
The daemon included with this plugin must be running. It will be started by
the plugin along with their other daemons when you run scripts/startdaemons.sh.
See the StatusNet README for more about queuing and daemons.
Settings
========
user*: username (screenname) to use when logging into AIM
password*: password for that user
* required
default values are in (parenthesis)
Example
=======
addPlugin('aim', array(
'user=>'...',
'password'=>'...'
));

100
plugins/Aim/aimmanager.php Normal file
View File

@ -0,0 +1,100 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, 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); }
/**
* AIM background connection manager for AIM-using queue handlers,
* allowing them to send outgoing messages on the right connection.
*
* Input is handled during socket select loop, keepalive pings during idle.
* Any incoming messages will be handled.
*
* In a multi-site queuedaemon.php run, one connection will be instantiated
* for each site being handled by the current process that has XMPP enabled.
*/
class AimManager extends ImManager
{
public $conn = null;
/**
* Initialize connection to server.
* @return boolean true on success
*/
public function start($master)
{
if(parent::start($master))
{
$this->connect();
return true;
}else{
return false;
}
}
public function getSockets()
{
$this->connect();
if($this->conn){
return array($this->conn->myConnection);
}else{
return array();
}
}
/**
* Process AIM events that have come in over the wire.
* @param resource $socket
*/
public function handleInput($socket)
{
common_log(LOG_DEBUG, "Servicing the AIM queue.");
$this->stats('aim_process');
$this->conn->receive();
}
function connect()
{
if (!$this->conn) {
$this->conn=new Aim($this->plugin->user,$this->plugin->password,4);
$this->conn->registerHandler("IMIn",array($this,"handle_aim_message"));
$this->conn->myServer="toc.oscar.aol.com";
$this->conn->signon();
$this->conn->setProfile(_m('Send me a message to post a notice'),false);
}
return $this->conn;
}
function handle_aim_message($data)
{
$this->plugin->enqueue_incoming_raw($data);
return true;
}
function send_raw_message($data)
{
$this->connect();
if (!$this->conn) {
return false;
}
$this->conn->sflapSend($data[0],$data[1],$data[2],$data[3]);
return true;
}
}

View File

@ -0,0 +1,169 @@
phpTOCLib version 1.0 RC1
This is released under the LGPL. AIM,TOC,OSCAR, and all other related protocols/terms are
copyright AOL/Time Warner. This project is in no way affiliated with them, nor is this
project supported by them.
Some of the code is loosely based off of a script by Jeffrey Grafton. Mainly the decoding of packets, and the
function for roasting passwords is entirly his.
TOC documentation used is available at http://simpleaim.sourceforge.net/docs/TOC.txt
About:
phpTOCLib aims to be a PHP equivalent to the PERL module NET::AIM. Due to some limitations,
this is difficult. Many features have been excluded in the name of simplicity, and leaves
you alot of room to code with externally, providing function access to the variables that
need them.
I have aimed to make this extensible, and easy to use, therefore taking away some built in
functionality that I had originally out in. This project comes after several months of
researching the TOC protocol.
example.php is included with the class. It needs to be executed from the command line
(ie:php -q testscript.php) and you need to call php.exe with the -q
example is provided as a demonstaration only. Though it creats a very simple, functional bot, it lacks any sort of commands, it merely resends the message it recieves in reverse.
Revisions:
-----------------------------------
by Rajiv Makhijani
(02/24/04)
- Fixed Bug in Setting Permit/Deny Mode
- Fixes so Uninitialized string offset notice doesn't appear
- Replaced New Lines Outputed for Each Flap Read with " . " so
that you can still tell it is active but it does not take so much space
- Removed "eh?" message
- Added MySQL Database Connection Message
- New Functions:
update_profile(profile data string, powered by boolean)
* The profile data string is the text that goes in the profile.
* The powered by boolean if set to true displays a link to the
sourceforge page of the script.
(02/28/04)
- Silent option added to set object not to output any information
- To follow silent rule use sEcho function instead of Echo
-----------------------------------
by Jeremy (pickleman78)
(05/26/04) beta 1 release
-Complete overhaul of class design and message handling
-Fixed bug involving sign off after long periods of idling
-Added new function $Aim->registerHandler
-Added the capability to handle all AIM messages
-Processing the messages is still the users responsibility
-Did a little bit of code cleanup
-Added a few internal functions to make the classes internal life easier
-Improved AIM server error message processing
-Updated this document (hopefully Rajiv will clean it up some, since I'm a terrible documenter)
-------------------------------------------------------------------------------------------------------------
Functions:
Several methods are provided in the class that allow for simple access to some of the
common features of AIM. Below are details.
$Aim->Aim($sn,$password,$pdmode, $silent=false)
The constructor, it takes 4 arguments.
$sn is your screen name
$password is you password, in plain text
$pdmode is the permit deny mode. This can be as follows:
1 - Allow All
2 - Deny All
3 - Permit only those on your permit list
4 - Permit all those not on your deny list
$silent if set to true prints out nothing
So, if your screen-name is JohnDoe746 and your password is fertu, and you want to allow
all users of the AIM server to contact you, you would code as follows
$myaim=new Aim("JohnDoe746","fertu",1);
$Aim->add_permit($buddy)
This adds the buddy passed to the function to your permit list.
ie: $myaim->add_permit("My friend22");
$Aim->block_buddy($buddy)
Blocks a user. This will switch your pd mode to 4. After using this, for the user to remain
out of contact with you, it is required to provide the constructor with a pd mode of 4
ie:$myaim->block_buddy("Annoying guy 4");
$Aim->send_im($to,$message,$auto=false)
Sends $message to $user. If you set the 3rd argument to true, then the recipient will receive it in
the same format as an away message. (Auto Response from me:)
A message longer than 65535 will be truncated
ie:$myaim->send_im("myfriend","This is a happy message");
$Aim->set_my_info()
Sends an update buddy command to the server and allows some internal values about yourself
to be set.
ie:$myaim->set_my_info();
$Aim->signon()
Call this to connect to the server. This must be called before any other methods will work
properly
ie:$mybot->signon();
$Aim->getLastReceived()
Returns $this->myLastReceived['decoded']. This should be the only peice of the gotten data
you need to concern yourself with. This is a preferred method of accessing this variable to prevent
accidental modification of $this->myLastReceived. Accidently modifying this variable can
cause some internal failures.
$Aim->read_from_aim()
This is a wrapper for $Aim->sflap_read(), and only returns the $this->myLastReceived['data']
portion of the message. It is preferred that you do not call $Aim->sflap_read() and use this
function instead. This function has a return value. Calling this prevents the need to call
$Aim->getLastReceived()
$Aim->setWarning($wl)
This allows you to update the bots warning level when warned.
$Aim->getBuddies()
Returns the $this->myBuddyList array. Use this instead of modifying the internal variable
$Aim->getPermit()
Returns the $this->myPermitList array. Use this instead of modifying the internal variable
$Aim->getBlocked()
Returns the $this->myBlockedList array. Use this instead of modifying the internal variable
$Aim->warn_user($user,$anon=false)
Warn $user. If anon is set to true, then it warns the user anonomously
$Aim->update_profile($information, $poweredby=false)
Updates Profile to $information. If $poweredby is true a link to
sourceforge page for this script is appended to profile
$Aim->registerHandler($function_name,$command)
This is by far the best thing about the new release.
For more information please reas supplement.txt. It is not included here because of the sheer size of the document.
supplement.txt contains full details on using registerHandler and what to expect for each input.
For convenience, I have provided some functions to simplify message processing.
They can be read about in the file "supplement.txt". I chose not to include the text here because it
is a huge document
There are a few things you should note about AIM
1)An incoming message has HTML tags in it. You are responsible for stripping those tags
2)Outgoing messages can have HTML tags, but will work fine if they don't. To include things
in the time feild next to the users name, send it as a comment
Conclusion:
The class is released under the LGPL. If you have any bug reports, comments, questions
feature requests, or want to help/show me what you've created with this(I am very interested in this),
please drop me an email: pickleman78@users.sourceforge.net. This code was written by
Jeremy(a.k.a pickleman78) and Rajiv M (a.k.a compwiz562).
Special thanks:
I'd like to thank all of the people who have contributed ideas, testing, bug reports, and code additions to
this project. I'd like to especially thank Rajiv, who has done do much for the project, and has kept this documnet
looking nice. He also has done alot of testing of this script too. I'd also like to thank SpazLink for his help in
testing. And finally I'd like to thank Jeffery Grafton, whose script inspired me to start this project.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,229 @@
<?php
//The following class was created June 30th 2004 by Jeremy(pickle)
//This class is designed to handle a direct connection
class Dconnect
{
var $sock;
var $lastReceived;
var $lastMessage;
var $connected;
var $cookie;
var $type=2;
var $connectedTo;
function Dconnect($ip,$port)
{
if(!$this->connect($ip,$port))
{
sEcho("Connection failed constructor");
$this->connected=false;
}
else
$this->connected=true;
$this->lastMessage="";
$this->lastReceived="";
}
function readDIM()
{
/*
if(!$this->stuffToRead())
{
sEcho("Nothing to read");
$this->lastMessage=$this->lastReceived="";
return false;
}
*/
$head=fread($this->sock,6);
if(strlen($head)<=0)
{
sEcho("The direct connection has been closed");
return false;
}
$minihead=unpack("a4ver/nsize",$head);
if($minihead['size'] <=0)
return;
$headerinfo=unpack("nchan/nsix/nzero/a6cookie/Npt1/Npt2/npt3/Nlen/Npt/npt0/ntype/Nzerom/a*sn",fread($this->sock,($minihead['size']-6)));
$allheader=array_merge($minihead,$headerinfo);
sEcho($allheader);
if($allheader['len']>0 && $allheader['len'] <= MAX_DIM_SIZE)
{
$left=$allheader['len'];
$stuff="";
$nonin=0;
while(strlen($stuff) < $allheader['len'] && $nonin<3)
{
$stuffg=fread($this->sock,$left);
if(strlen($stuffg)<0)
{
$nonin++;
continue;
}
$left=$left - strlen($stuffg);
$stuff.=$stuffg;
}
$data=unpack("a*decoded",$stuff);
}
else if($allheader['len'] > MAX_DIM_SIZE)
{
$data['decoded']="too big";
}
else
$data['decoded']="";
$all=array_merge($allheader,$data);
$this->lastReceived=$all;
$this->lastMessage=$all['decoded'];
//$function=$this->DimInf . "(\$all);";
//eval($function);
return $all;
}
function sendMessage($message,$sn)
{
//Make the "mini header"
$minihead=pack("a4n","ODC2",76);
$header=pack("nnna6NNnNNnnNa*",1,6,0,$this->cookie,0,0,0,strlen($message),0,0,96,0,$sn);
$bighead=$minihead . $header;
while(strlen($bighead)<76)
$bighead.=pack("c",0);
$tosend=$bighead . pack("a*",$message);
$w=array($this->sock);
stream_select($r=NULL,$w,$e=NULL,NULL);
//Now send it all
fputs($this->sock,$tosend,strlen($tosend));
}
function stuffToRead()
{
//$info=stream_get_meta_data($this->sock);
//sEcho($info);
$s=array($this->sock);
$changed=stream_select($s,$fds=NULL,$m=NULL,0,20000);
return ($changed>0);
}
function close()
{
$this->connected=false;
return fclose($this->sock);
}
function connect($ip,$port)
{
$this->sock=fsockopen($ip,$port,$en,$es,3);
if(!$this->sock)
{ sEcho("Connection failed");
$this->sock=null;
return false;
}
return true;
}
}
class FileSendConnect
{
var $sock;
var $lastReceived;
var $lastMessage;
var $connected;
var $cookie;
var $tpye=3;
function FileSendConnect($ip,$port)
{
if(!$this->connect($ip,$port))
{
sEcho("Connection failed constructor");
$this->connected=false;
}
else
$this->connected=true;
$this->lastMessage="";
$this->lastReceived="";
}
function readDIM()
{
if(!$this->stuffToRead())
{
sEcho("Nothing to read");
$this->lastMessage=$this->lastReceived="";
return;
}
$minihead=unpack("a4ver/nsize",fread($this->sock,6));
if($minihead['size'] <=0)
return;
$headerinfo=unpack("nchan/nsix/nzero/a6cookie/Npt1/Npt2/npt3/Nlen/Npt/npt0/ntype/Nzerom/a*sn",fread($this->sock,($minihead['size']-6)));
$allheader=array_merge($minihead,$headerinfo);
sEcho($allheader);
if($allheader['len']>0)
$data=unpack("a*decoded",fread($this->sock,$allheader['len']));
else
$data['decoded']="";
$all=array_merge($allheader,$data);
$this->lastReceived=$all;
$this->lastMessage=$all['decoded'];
//$function=$this->DimInf . "(\$all);";
//eval($function);
return $all;
}
function sendMessage($message,$sn)
{
//Make the "mini header"
$minihead=pack("a4n","ODC2",76);
$header=pack("nnna6NNnNNnnNa*",1,6,0,$this->cookie,0,0,0,strlen($message),0,0,96,0,$sn);
$bighead=$minihead . $header;
while(strlen($bighead)<76)
$bighead.=pack("c",0);
$tosend=$bighead . pack("a*",$message);
//Now send it all
fwrite($this->sock,$tosend,strlen($tosend));
}
function stuffToRead()
{
//$info=stream_get_meta_data($this->sock);
//sEcho($info);
$s=array($this->sock);
$changed=stream_select($s,$fds=NULL,$m=NULL,1);
return ($changed>0);
}
function close()
{
$this->connected=false;
fclose($this->sock);
unset($this->sock);
return true;
}
function connect($ip,$port)
{
$this->sock=fsockopen($ip,$port,$en,$es,3);
if(!$this->sock)
{ sEcho("Connection failed to" . $ip . ":" . $port);
$this->sock=null;
return false;
}
return true;
}
}

View File

@ -57,12 +57,14 @@ class ImapManager extends IoManager
}
/**
* Tell the i/o master we need one instance for each supporting site
* being handled in this process.
* Tell the i/o master we need one instance globally.
* Since this is a plugin manager, the plugin class itself will
* create one instance per site. This prevents the IoMaster from
* making more instances.
*/
public static function multiSite()
{
return IoManager::INSTANCE_PER_SITE;
return IoManager::GLOBAL_SINGLE_ONLY;
}
/**

View File

@ -2,7 +2,7 @@
/**
* StatusNet, the distributed open-source microblogging tool
*
* Queue-mediated proxy class for outgoing XMPP messages.
* Instead of sending XMPP messages, retrieve the raw XML that would be sent
*
* PHP version 5
*
@ -31,10 +31,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/lib/jabber.php';
class Queued_XMPP extends XMPPHP_XMPP
class Fake_XMPP extends XMPPHP_XMPP
{
public $would_be_sent = null;
/**
* Constructor
*
@ -63,54 +63,41 @@ class Queued_XMPP extends XMPPHP_XMPP
*/
public function send($msg, $timeout=NULL)
{
$qm = QueueManager::get();
$qm->enqueue(strval($msg), 'xmppout');
}
/**
* Since we'll be getting input through a queue system's run loop,
* we'll process one standalone message at a time rather than our
* own XMPP message pump.
*
* @param string $message
*/
public function processMessage($message) {
$frame = array_shift($this->frames);
xml_parse($this->parser, $frame->body, false);
$this->would_be_sent = $msg;
}
//@{
/**
* Stream i/o functions disabled; push input through processMessage()
* Stream i/o functions disabled; only do output
*/
public function connect($timeout = 30, $persistent = false, $sendinit = true)
{
throw new Exception("Can't connect to server from XMPP queue proxy.");
throw new Exception("Can't connect to server from fake XMPP.");
}
public function disconnect()
{
throw new Exception("Can't connect to server from XMPP queue proxy.");
throw new Exception("Can't connect to server from fake XMPP.");
}
public function process()
{
throw new Exception("Can't read stream from XMPP queue proxy.");
throw new Exception("Can't read stream from fake XMPP.");
}
public function processUntil($event, $timeout=-1)
{
throw new Exception("Can't read stream from XMPP queue proxy.");
throw new Exception("Can't read stream from fake XMPP.");
}
public function read()
{
throw new Exception("Can't read stream from XMPP queue proxy.");
throw new Exception("Can't read stream from fake XMPP.");
}
public function readyToProcess()
{
throw new Exception("Can't read stream from XMPP queue proxy.");
throw new Exception("Can't read stream from fake XMPP.");
}
//@}
}

35
plugins/Xmpp/README Normal file
View File

@ -0,0 +1,35 @@
The XMPP plugin allows users to send and receive notices over the XMPP/Jabber/GTalk network.
Installation
============
add "addPlugin('xmpp',
array('setting'=>'value', 'setting2'=>'value2', ...);"
to the bottom of your config.php
The daemon included with this plugin must be running. It will be started by
the plugin along with their other daemons when you run scripts/startdaemons.sh.
See the StatusNet README for more about queuing and daemons.
Settings
========
user*: user part of the jid
server*: server part of the jid
resource: resource part of the jid
port (5222): port on which to connect to the server
encryption (true): use encryption on the connection
host (same as server): host to connect to. Usually, you won't set this.
debug (false): log extra debug info
public: list of jid's that should get the public feed (firehose)
* required
default values are in (parenthesis)
Example
=======
addPlugin('xmpp', array(
'user=>'update',
'server=>'identi.ca',
'password'=>'...',
'public'=>array('bob@aol.com', 'sue@google.com')
));

View File

@ -0,0 +1,43 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
* Send and receive notices using the Jabber network
*
* PHP version 5
*
* 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 Jabber
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
class Sharing_XMPP extends XMPPHP_XMPP
{
function getSocket()
{
return $this->socket;
}
}

247
plugins/Xmpp/XmppPlugin.php Normal file
View File

@ -0,0 +1,247 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2009, StatusNet, Inc.
*
* Send and receive notices using the XMPP network
*
* PHP version 5
*
* 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 IM
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
// This check helps protect against security problems;
// your code file can't be executed directly from the web.
exit(1);
}
/**
* Plugin for XMPP
*
* @category Plugin
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class XmppPlugin extends ImPlugin
{
public $server = null;
public $port = 5222;
public $user = 'update';
public $resource = null;
public $encryption = true;
public $password = null;
public $host = null; // only set if != server
public $debug = false; // print extra debug info
public $transport = 'xmpp';
protected $fake_xmpp;
function getDisplayName(){
return _m('XMPP/Jabber/GTalk');
}
function normalize($screenname)
{
if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $screenname, $matches)) {
$node = $matches[1];
$server = $matches[2];
return strtolower($node.'@'.$server);
} else {
return null;
}
}
function daemon_screenname()
{
$ret = $this->user . '@' . $this->server;
if($this->resource)
{
return $ret . '/' . $this->resource;
}else{
return $ret;
}
}
function validate($screenname)
{
// Cheap but effective
return Validate::email($screenname);
}
/**
* Load related modules when needed
*
* @param string $cls Name of the class to be loaded
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onAutoload($cls)
{
$dir = dirname(__FILE__);
switch ($cls)
{
case 'Sharing_XMPP':
case 'Fake_XMPP':
include_once $dir . '/'.$cls.'.php';
return false;
case 'XmppManager':
include_once $dir . '/'.strtolower($cls).'.php';
return false;
default:
return true;
}
}
function onStartImDaemonIoManagers(&$classes)
{
parent::onStartImDaemonIoManagers(&$classes);
$classes[] = new XmppManager($this); // handles pings/reconnects
return true;
}
function microiduri($screenname)
{
return 'xmpp:' . $screenname;
}
function send_message($screenname, $body)
{
$this->fake_xmpp->message($screenname, $body, 'chat');
$this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent);
return true;
}
function send_notice($screenname, $notice)
{
$msg = $this->format_notice($notice);
$entry = $this->format_entry($notice);
$this->fake_xmpp->message($screenname, $msg, 'chat', null, $entry);
$this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent);
return true;
}
/**
* extra information for XMPP messages, as defined by Twitter
*
* @param Profile $profile Profile of the sending user
* @param Notice $notice Notice being sent
*
* @return string Extra information (Atom, HTML, addresses) in string format
*/
function format_entry($notice)
{
$profile = $notice->getProfile();
$entry = $notice->asAtomEntry(true, true);
$xs = new XMLStringer();
$xs->elementStart('html', array('xmlns' => 'http://jabber.org/protocol/xhtml-im'));
$xs->elementStart('body', array('xmlns' => 'http://www.w3.org/1999/xhtml'));
$xs->element('a', array('href' => $profile->profileurl),
$profile->nickname);
$xs->text(": ");
if (!empty($notice->rendered)) {
$xs->raw($notice->rendered);
} else {
$xs->raw(common_render_content($notice->content, $notice));
}
$xs->text(" ");
$xs->element('a', array(
'href'=>common_local_url('conversation',
array('id' => $notice->conversation)).'#notice-'.$notice->id
),sprintf(_('[%s]'),$notice->id));
$xs->elementEnd('body');
$xs->elementEnd('html');
$html = $xs->getString();
return $html . ' ' . $entry;
}
function receive_raw_message($pl)
{
$from = $this->normalize($pl['from']);
if ($pl['type'] != 'chat') {
common_log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from.");
return true;
}
if (mb_strlen($pl['body']) == 0) {
common_log(LOG_WARNING, "Ignoring message with empty body from $from.");
return true;
}
return $this->handle_incoming($from, $pl['body']);
}
function initialize(){
if(!isset($this->server)){
throw new Exception("must specify a server");
}
if(!isset($this->port)){
throw new Exception("must specify a port");
}
if(!isset($this->user)){
throw new Exception("must specify a user");
}
if(!isset($this->password)){
throw new Exception("must specify a password");
}
$this->fake_xmpp = new Fake_XMPP($this->host ?
$this->host :
$this->server,
$this->port,
$this->user,
$this->password,
$this->resource,
$this->server,
$this->debug ?
true : false,
$this->debug ?
XMPPHP_Log::LEVEL_VERBOSE : null
);
return true;
}
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'XMPP',
'version' => STATUSNET_VERSION,
'author' => 'Craig Andrews, Evan Prodromou',
'homepage' => 'http://status.net/wiki/Plugin:XMPP',
'rawdescription' =>
_m('The XMPP plugin allows users to send and receive notices over the XMPP/Jabber network.'));
return true;
}
}

View File

@ -0,0 +1,279 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2008, 2009, 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); }
/**
* XMPP background connection manager for XMPP-using queue handlers,
* allowing them to send outgoing messages on the right connection.
*
* Input is handled during socket select loop, keepalive pings during idle.
* Any incoming messages will be handled.
*
* In a multi-site queuedaemon.php run, one connection will be instantiated
* for each site being handled by the current process that has XMPP enabled.
*/
class XmppManager extends ImManager
{
protected $lastping = null;
protected $pingid = null;
public $conn = null;
const PING_INTERVAL = 120;
/**
* Initialize connection to server.
* @return boolean true on success
*/
public function start($master)
{
if(parent::start($master))
{
$this->connect();
return true;
}else{
return false;
}
}
function send_raw_message($data)
{
$this->connect();
if (!$this->conn || $this->conn->isDisconnected()) {
return false;
}
$this->conn->send($data);
return true;
}
/**
* Message pump is triggered on socket input, so we only need an idle()
* call often enough to trigger our outgoing pings.
*/
function timeout()
{
return self::PING_INTERVAL;
}
/**
* Process XMPP events that have come in over the wire.
* @fixme may kill process on XMPP error
* @param resource $socket
*/
public function handleInput($socket)
{
# Process the queue for as long as needed
try {
common_log(LOG_DEBUG, "Servicing the XMPP queue.");
$this->stats('xmpp_process');
$this->conn->processTime(0);
} catch (XMPPHP_Exception $e) {
common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
die($e->getMessage());
}
}
/**
* Lists the IM connection socket to allow i/o master to wake
* when input comes in here as well as from the queue source.
*
* @return array of resources
*/
public function getSockets()
{
$this->connect();
if($this->conn){
return array($this->conn->getSocket());
}else{
return array();
}
}
/**
* Idle processing for io manager's execution loop.
* Send keepalive pings to server.
*
* Side effect: kills process on exception from XMPP library.
*
* @fixme non-dying error handling
*/
public function idle($timeout=0)
{
$now = time();
if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) {
try {
$this->send_ping();
} catch (XMPPHP_Exception $e) {
common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage());
die($e->getMessage());
}
}
}
function connect()
{
if (!$this->conn || $this->conn->isDisconnected()) {
$resource = 'queue' . posix_getpid();
$this->conn = new Sharing_XMPP($this->plugin->host ?
$this->plugin->host :
$this->plugin->server,
$this->plugin->port,
$this->plugin->user,
$this->plugin->password,
$this->plugin->resource,
$this->plugin->server,
$this->plugin->debug ?
true : false,
$this->plugin->debug ?
XMPPHP_Log::LEVEL_VERBOSE : null
);
if (!$this->conn) {
return false;
}
$this->conn->addEventHandler('message', 'handle_xmpp_message', $this);
$this->conn->addEventHandler('reconnect', 'handle_xmpp_reconnect', $this);
$this->conn->setReconnectTimeout(600);
$this->conn->autoSubscribe();
$this->conn->useEncryption($this->plugin->encryption);
try {
$this->conn->connect(true); // true = persistent connection
} catch (XMPPHP_Exception $e) {
common_log(LOG_ERR, $e->getMessage());
return false;
}
$this->conn->processUntil('session_start');
$this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100);
}
return $this->conn;
}
function send_ping()
{
$this->connect();
if (!$this->conn || $this->conn->isDisconnected()) {
return false;
}
$now = time();
if (!isset($this->pingid)) {
$this->pingid = 0;
} else {
$this->pingid++;
}
common_log(LOG_DEBUG, "Sending ping #{$this->pingid}");
$this->conn->send("<iq from='{" . $this->plugin->daemon_screenname() . "}' to='{$this->plugin->server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>");
$this->lastping = $now;
return true;
}
function handle_xmpp_message(&$pl)
{
$this->plugin->enqueue_incoming_raw($pl);
return true;
}
/**
* Callback for Jabber reconnect event
* @param $pl
*/
function handle_xmpp_reconnect(&$pl)
{
common_log(LOG_NOTICE, 'XMPP reconnected');
$this->conn->processUntil('session_start');
$this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100);
}
/**
* sends a presence stanza on the XMPP network
*
* @param string $status current status, free-form string
* @param string $show structured status value
* @param string $to recipient of presence, null for general
* @param string $type type of status message, related to $show
* @param int $priority priority of the presence
*
* @return boolean success value
*/
function send_presence($status, $show='available', $to=null,
$type = 'available', $priority=null)
{
$this->connect();
if (!$this->conn || $this->conn->isDisconnected()) {
return false;
}
$this->conn->presence($status, $show, $to, $type, $priority);
return true;
}
/**
* sends a "special" presence stanza on the XMPP network
*
* @param string $type Type of presence
* @param string $to JID to send presence to
* @param string $show show value for presence
* @param string $status status value for presence
*
* @return boolean success flag
*
* @see send_presence()
*/
function special_presence($type, $to=null, $show=null, $status=null)
{
// FIXME: why use this instead of send_presence()?
$this->connect();
if (!$this->conn || $this->conn->isDisconnected()) {
return false;
}
$to = htmlspecialchars($to);
$status = htmlspecialchars($status);
$out = "<presence";
if ($to) {
$out .= " to='$to'";
}
if ($type) {
$out .= " type='$type'";
}
if ($show == 'available' and !$status) {
$out .= "/>";
} else {
$out .= ">";
if ($show && ($show != 'available')) {
$out .= "<show>$show</show>";
}
if ($status) {
$out .= "<status>$status</status>";
}
$out .= "</presence>";
}
$this->conn->send($out);
return true;
}
}

View File

@ -39,9 +39,7 @@ $daemons = array();
$daemons[] = INSTALLDIR.'/scripts/queuedaemon.php';
if(common_config('xmpp','enabled')) {
$daemons[] = INSTALLDIR.'/scripts/xmppdaemon.php';
}
$daemons[] = INSTALLDIR.'/scripts/imdaemon.php';
if (Event::handle('GetValidDaemons', array(&$daemons))) {
foreach ($daemons as $daemon) {

View File

@ -23,34 +23,32 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'fi::';
$longoptions = array('id::', 'foreground');
$helptext = <<<END_OF_XMPP_HELP
Daemon script for receiving new notices from Jabber users.
$helptext = <<<END_OF_IM_HELP
Daemon script for receiving new notices from IM users.
-i --id Identity (default none)
-f --foreground Stay in the foreground (default background)
END_OF_XMPP_HELP;
END_OF_IM_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
require_once INSTALLDIR . '/lib/jabber.php';
class XMPPDaemon extends SpawningDaemon
class ImDaemon extends SpawningDaemon
{
function __construct($id=null, $daemonize=true, $threads=1)
{
if ($threads != 1) {
// This should never happen. :)
throw new Exception("XMPPDaemon can must run single-threaded");
throw new Exception("IMDaemon can must run single-threaded");
}
parent::__construct($id, $daemonize, $threads);
}
function runThread()
{
common_log(LOG_INFO, 'Waiting to listen to XMPP and queues');
common_log(LOG_INFO, 'Waiting to listen to IM connections and queues');
$master = new XmppMaster($this->get_id());
$master = new ImMaster($this->get_id());
$master->init();
$master->service();
@ -61,7 +59,7 @@ class XMPPDaemon extends SpawningDaemon
}
class XmppMaster extends IoMaster
class ImMaster extends IoMaster
{
/**
* Initialize IoManagers for the currently configured site
@ -69,20 +67,17 @@ class XmppMaster extends IoMaster
*/
function initManagers()
{
// @fixme right now there's a hack in QueueManager to determine
// which queues to subscribe to based on the master class.
$this->instantiate('QueueManager');
$this->instantiate('XmppManager');
$classes = array();
if (Event::handle('StartImDaemonIoManagers', array(&$classes))) {
$classes[] = 'QueueManager';
}
Event::handle('EndImDaemonIoManagers', array(&$classes));
foreach ($classes as $class) {
$this->instantiate($class);
}
}
}
// Abort immediately if xmpp is not enabled, otherwise the daemon chews up
// lots of CPU trying to connect to unconfigured servers
if (common_config('xmpp','enabled')==false) {
print "Aborting daemon - xmpp is disabled\n";
exit();
}
if (have_option('i', 'id')) {
$id = get_option_value('i', 'id');
} else if (count($args) > 0) {
@ -93,6 +88,6 @@ if (have_option('i', 'id')) {
$foreground = have_option('f', 'foreground');
$daemon = new XMPPDaemon($id, !$foreground);
$daemon = new ImDaemon($id, !$foreground);
$daemon->runOnce();

View File

@ -21,7 +21,7 @@
define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
$shortoptions = 'fi:at:';
$longoptions = array('id=', 'foreground', 'all', 'threads=', 'skip-xmpp', 'xmpp-only');
$longoptions = array('id=', 'foreground', 'all', 'threads=');
/**
* Attempts to get a count of the processors available on the current system
@ -163,13 +163,6 @@ if (!$threads) {
$daemonize = !(have_option('f') || have_option('--foreground'));
$all = have_option('a') || have_option('--all');
if (have_option('--skip-xmpp')) {
define('XMPP_EMERGENCY_FLAG', true);
}
if (have_option('--xmpp-only')) {
define('XMPP_ONLY_FLAG', true);
}
$daemon = new QueueDaemon($id, $daemonize, $threads, $all);
$daemon->runOnce();

View File

@ -23,8 +23,8 @@
SDIR=`dirname $0`
DIR=`php $SDIR/getpiddir.php`
for f in jabberhandler ombhandler publichandler smshandler pinghandler \
xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \
for f in ombhandler smshandler pinghandler \
twitterhandler facebookhandler \
twitterstatusfetcher synctwitterfriends pluginhandler rsscloudhandler; do
FILES="$DIR/$f.*.pid"