Merge ActivitySpam plugin

This commit is contained in:
Evan Prodromou 2012-04-17 09:02:48 -04:00
commit 7a9777df05
12 changed files with 1604 additions and 0 deletions

View File

@ -0,0 +1,318 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011,2012, StatusNet, Inc.
*
* ActivitySpam Plugin
*
* 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 Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011,2012 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);
}
/**
* Check new notices with activity spam service.
*
* @category Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011,2012 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class ActivitySpamPlugin extends Plugin
{
public $server = null;
public $hideSpam = false;
const REVIEWSPAM = 'ActivitySpamPlugin::REVIEWSPAM';
const TRAINSPAM = 'ActivitySpamPlugin::TRAINSPAM';
/**
* Initializer
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function initialize()
{
$this->filter = new SpamFilter(common_config('activityspam', 'server'),
common_config('activityspam', 'consumerkey'),
common_config('activityspam', 'secret'));
$this->hideSpam = common_config('activityspam', 'hidespam');
return true;
}
/**
* Database schema setup
*
* @see Schema
* @see ColumnDef
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onCheckSchema()
{
$schema = Schema::get();
$schema->ensureTable('spam_score', Spam_score::schemaDef());
Spam_score::upgrade();
return true;
}
/**
* 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 'TrainAction':
case 'SpamAction':
include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
case 'Spam_score':
include_once $dir . '/'.$cls.'.php';
return false;
case 'SpamFilter':
case 'SpamNoticeStream':
case 'TrainSpamForm':
case 'TrainHamForm':
include_once $dir . '/'.strtolower($cls).'.php';
return false;
default:
return true;
}
}
/**
* When a notice is saved, check its spam score
*
* @param Notice $notice Notice that was just saved
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onEndNoticeSave($notice)
{
try {
$result = $this->filter->test($notice);
$score = Spam_score::saveNew($notice, $result);
$this->log(LOG_INFO, "Notice " . $notice->id . " has spam score " . $score->score);
} catch (Exception $e) {
// Log but continue
$this->log(LOG_ERR, $e->getMessage());
}
return true;
}
function onNoticeDeleteRelated($notice) {
$score = Spam_score::staticGet('notice_id', $notice->id);
if (!empty($score)) {
$score->delete();
}
return true;
}
function onUserRightsCheck($profile, $right, &$result) {
switch ($right) {
case self::REVIEWSPAM:
case self::TRAINSPAM:
$result = ($profile->hasRole(Profile_role::MODERATOR) || $profile->hasRole('modhelper'));
return false;
default:
return true;
}
}
function onGetSpamFilter(&$filter) {
$filter = $this->filter;
return false;
}
function onEndShowNoticeOptionItems($nli)
{
$profile = Profile::current();
if (!empty($profile) && $profile->hasRight(self::TRAINSPAM)) {
$notice = $nli->getNotice();
$out = $nli->getOut();
if (!empty($notice)) {
$score = $this->getScore($notice);
if (empty($score)) {
$this->debug("No score for notice " . $notice->id);
// XXX: show a question-mark or something
} else if ($score->is_spam) {
$form = new TrainHamForm($out, $notice);
$form->show();
} else if (!$score->is_spam) {
$form = new TrainSpamForm($out, $notice);
$form->show();
}
}
}
return true;
}
/**
* Map URLs to actions
*
* @param Net_URL_Mapper $m path-to-action mapper
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onRouterInitialized($m)
{
$m->connect('main/train/spam',
array('action' => 'train', 'category' => 'spam'));
$m->connect('main/train/ham',
array('action' => 'train', 'category' => 'ham'));
$m->connect('main/spam',
array('action' => 'spam'));
return true;
}
function onEndShowStyles($action)
{
$action->element('style', null,
'.form-train-spam input.submit { background: url('.$this->path('icons/bullet_black.png').') no-repeat 0px 0px } ' . "\n" .
'.form-train-ham input.submit { background: url('.$this->path('icons/exclamation.png').') no-repeat 0px 0px } ');
return true;
}
function onEndPublicGroupNav($nav)
{
$user = common_current_user();
if (!empty($user) && $user->hasRight(self::REVIEWSPAM)) {
$nav->out->menuItem(common_local_url('spam'),
_m('MENU','Spam'),
// TRANS: Menu item title in search group navigation panel.
_('Notices marked as spam'),
$nav->actionName == 'spam',
'nav_timeline_spam');
}
return true;
}
function onPluginVersion(&$versions)
{
$versions[] = array('name' => 'ActivitySpam',
'version' => STATUSNET_VERSION,
'author' => 'Evan Prodromou',
'homepage' => 'http://status.net/wiki/Plugin:ActivitySpam',
'description' =>
_m('Test notices against the Activity Spam service.'));
return true;
}
function getScore($notice)
{
$score = Spam_score::staticGet('notice_id', $notice->id);
if (!empty($score)) {
return $score;
}
try {
$result = $this->filter->test($notice);
$score = Spam_score::saveNew($notice, $result);
$this->log(LOG_INFO, "Notice " . $notice->id . " has spam score " . $score->score);
} catch (Exception $e) {
// Log but continue
$this->log(LOG_ERR, $e->getMessage());
$score = null;
}
return $score;
}
function onStartReadWriteTables(&$alwaysRW, &$rwdb) {
$alwaysRW[] = 'spam_score';
return true;
}
function onEndNoticeInScope($notice, $profile, &$bResult)
{
if ($this->hideSpam) {
if ($bResult) {
$score = Spam_score::staticGet('notice_id', $notice->id);
if (!empty($score) && $score->is_spam) {
if (empty($profile) ||
($profile->id !== $notice->profile_id &&
!$profile->hasRight(self::REVIEWSPAM))) {
$bResult = false;
}
}
}
}
return true;
}
/**
* Pre-cache our spam scores if needed.
*/
function onEndNoticeListPrefill(&$notices, &$profiles, $avatarSize) {
if ($this->hideSpam) {
foreach ($notices as $notice) {
$ids[] = $notice->id;
}
Memcached_DataObject::multiGet('Spam_score', 'notice_id', $ids);
}
return true;
}
}

View File

@ -0,0 +1,216 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Score of a notice by activity spam service
*
* 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 Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/**
* Score of a notice per the activity spam service
*
* @category Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*
* @see DB_DataObject
*/
class Spam_score extends Managed_DataObject
{
const MAX_SCALE = 10000;
public $__table = 'spam_score'; // table name
public $notice_id; // int
public $score; // float
public $created; // datetime
/**
* Get an instance by key
*
* @param string $k Key to use to lookup (usually 'notice_id' for this class)
* @param mixed $v Value to lookup
*
* @return Spam_score object found, or null for no hits
*
*/
function staticGet($k, $v=null)
{
return Managed_DataObject::staticGet('Spam_score', $k, $v);
}
function saveNew($notice, $result) {
$score = new Spam_score();
$score->notice_id = $notice->id;
$score->score = $result->probability;
$score->is_spam = $result->isSpam;
$score->scaled = Spam_score::scale($score->score);
$score->created = common_sql_now();
$score->notice_created = $notice->created;
$score->insert();
self::blow('spam_score:notice_ids');
return $score;
}
function save($notice, $result) {
$orig = null;
$score = Spam_score::staticGet('notice_id', $notice->id);
if (empty($score)) {
$score = new Spam_score();
} else {
$orig = clone($score);
}
$score->notice_id = $notice->id;
$score->score = $result->probability;
$score->is_spam = $result->isSpam;
$score->scaled = Spam_score::scale($score->score);
$score->created = common_sql_now();
$score->notice_created = $notice->created;
if (empty($orig)) {
$score->insert();
} else {
$score->update($orig);
}
self::blow('spam_score:notice_ids');
return $score;
}
function delete()
{
self::blow('spam_score:notice_ids');
self::blow('spam_score:notice_ids;last');
parent::delete();
}
/**
* The One True Thingy that must be defined and declared.
*/
public static function schemaDef()
{
return array(
'description' => 'score of the notice per activityspam',
'fields' => array(
'notice_id' => array('type' => 'int',
'not null' => true,
'description' => 'notice getting scored'),
'score' => array('type' => 'double',
'not null' => true,
'description' => 'score for the notice (0.0, 1.0)'),
'scaled' => array('type' => 'int',
'description' => 'scaled score for the notice (0, 10000)'),
'is_spam' => array('type' => 'tinyint',
'description' => 'flag for spamosity'),
'created' => array('type' => 'datetime',
'not null' => true,
'description' => 'date this record was created'),
'notice_created' => array('type' => 'datetime',
'description' => 'date the notice was created'),
),
'primary key' => array('notice_id'),
'foreign keys' => array(
'spam_score_notice_id_fkey' => array('notice', array('notice_id' => 'id')),
),
'indexes' => array(
'spam_score_created_idx' => array('created'),
'spam_score_scaled_idx' => array('scaled'),
),
);
}
public static function upgrade()
{
Spam_score::upgradeScaled();
Spam_score::upgradeIsSpam();
Spam_score::upgradeNoticeCreated();
}
protected static function upgradeScaled()
{
$score = new Spam_score();
$score->whereAdd('scaled IS NULL');
if ($score->find()) {
while ($score->fetch()) {
$orig = clone($score);
$score->scaled = Spam_score::scale($score->score);
$score->update($orig);
}
}
}
protected static function upgradeIsSpam()
{
$score = new Spam_score();
$score->whereAdd('is_spam IS NULL');
if ($score->find()) {
while ($score->fetch()) {
$orig = clone($score);
$score->is_spam = ($score->score >= 0.90) ? 1 : 0;
$score->update($orig);
}
}
}
protected static function upgradeNoticeCreated()
{
$score = new Spam_score();
$score->whereAdd('notice_created IS NULL');
if ($score->find()) {
while ($score->fetch()) {
$notice = Notice::staticGet('id', $score->notice_id);
if (!empty($notice)) {
$orig = clone($score);
$score->notice_created = $notice->created;
$score->update($orig);
}
}
}
}
public static function scale($score)
{
$raw = round($score * Spam_score::MAX_SCALE);
return max(0, min(Spam_score::MAX_SCALE, $raw));
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

View File

@ -0,0 +1,105 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2012 StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../../..'));
$shortoptions = 'i:n:a';
$longoptions = array('id=', 'nickname=', 'all');
$helptext = <<<END_OF_TESTUSER_HELP
testuser.php [options]
Test user activities against the spam filter
-i --id ID of user to export
-n --nickname nickname of the user to export
-a --all All users
END_OF_TESTUSER_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
function testAllUsers($filter) {
$found = false;
$offset = 0;
$limit = 1000;
do {
$user = new User();
$user->orderBy('created');
$user->limit($offset, $limit);
$found = $user->find();
if ($found) {
while ($user->fetch()) {
try {
testUser($filter, $user);
} catch (Exception $e) {
printfnq("ERROR testing user %s\n: %s", $user->nickname, $e->getMessage());
}
}
$offset += $found;
}
} while ($found > 0);
}
function testUser($filter, $user) {
printfnq("Testing user %s\n", $user->nickname);
$profile = Profile::staticGet('id', $user->id);
$str = new ProfileNoticeStream($profile, $profile);
$offset = 0;
$limit = 100;
do {
$notice = $str->getNotices($offset, $limit);
while ($notice->fetch()) {
try {
printfv("Testing notice %d...", $notice->id);
$result = $filter->test($notice);
Spam_score::save($notice, $result);
printfv("%s\n", ($result->isSpam) ? "SPAM" : "HAM");
} catch (Exception $e) {
printfnq("ERROR testing notice %d: %s\n", $notice->id, $e->getMessage());
}
}
$offset += $notice->N;
} while ($notice->N > 0);
}
try {
$filter = null;
Event::handle('GetSpamFilter', array(&$filter));
if (empty($filter)) {
throw new Exception(_("No spam filter."));
}
if (have_option('a', 'all')) {
testAllUsers($filter);
} else {
$user = getUser();
testUser($filter, $user);
}
} catch (Exception $e) {
print $e->getMessage()."\n";
exit(1);
}

View File

@ -0,0 +1,81 @@
<?php
/*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2012 StatusNet, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../../..'));
$shortoptions = 'i:n:t:';
$longoptions = array('id=', 'nickname=', 'category=');
$helptext = <<<END_OF_TRAINUSER_HELP
trainuser.php [options]
Train user activities against the spam filter
-i --id ID of user to export
-n --nickname nickname of the user to export
-t --category Category; one of "spam" or "ham"
END_OF_TRAINUSER_HELP;
require_once INSTALLDIR.'/scripts/commandline.inc';
function trainUser($filter, $user, $category) {
printfnq("Training user %s\n", $user->nickname);
$profile = Profile::staticGet('id', $user->id);
$str = new ProfileNoticeStream($profile, $profile);
$offset = 0;
$limit = 100;
do {
$notice = $str->getNotices($offset, $limit);
while ($notice->fetch()) {
try {
printfv("Training notice %d...", $notice->id);
$filter->trainOnError($notice, $category);
$result = $filter->test($notice);
$score = Spam_score::save($notice, $result);
printfv("%s\n", ($result->isSpam) ? "SPAM" : "HAM");
} catch (Exception $e) {
printfnq("ERROR training notice %d\n: %s", $notice->id, $e->getMessage());
}
}
$offset += $notice->N;
} while ($notice->N > 0);
}
try {
$filter = null;
Event::handle('GetSpamFilter', array(&$filter));
if (empty($filter)) {
throw new Exception(_("No spam filter."));
}
$user = getUser();
$category = get_option_value('t', 'category');
if ($category !== SpamFilter::HAM &&
$category !== SpamFilter::SPAM) {
throw new Exception(_("No such category."));
}
trainUser($filter, $user, $category);
} catch (Exception $e) {
print $e->getMessage()."\n";
exit(1);
}

View File

@ -0,0 +1,165 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2012, StatusNet, Inc.
*
* Stream of latest spam messages
*
* 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 Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2012 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);
}
require_once INSTALLDIR.'/lib/noticelist.php';
/**
* SpamAction
*
* Shows the latest spam on the service
*
* @category Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2012 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class SpamAction extends Action
{
var $page = null;
var $notices = null;
function title() {
return _("Latest Spam");
}
/**
* For initializing members of the class.
*
* @param array $argarray misc. arguments
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
$this->page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
// User must be logged in.
$user = common_current_user();
if (empty($user)) {
throw new ClientException(_("You must be logged in to review."), 403);
}
// User must have the right to review spam
if (!$user->hasRight(ActivitySpamPlugin::REVIEWSPAM)) {
throw new ClientException(_('You cannot review spam on this site.'), 403);
}
$stream = new SpamNoticeStream($user->getProfile());
$this->notices = $stream->getNotices(($this->page-1)*NOTICES_PER_PAGE,
NOTICES_PER_PAGE + 1);
if($this->page > 1 && $this->notices->N == 0) {
throw new ClientException(_('No such page.'), 404);
}
return true;
}
/**
* Handler method
*
* @param array $argarray is ignored since it's now passed in in prepare()
*
* @return void
*/
function handle($argarray=null)
{
parent::handle($args);
$this->showPage();
}
/**
* Fill the content area
*
* Shows a list of the notices in the public stream, with some pagination
* controls.
*
* @return void
*/
function showContent()
{
$nl = new NoticeList($this->notices, $this);
$cnt = $nl->show();
if ($cnt == 0) {
$this->showEmptyList();
}
$this->pagination($this->page > 1,
$cnt > NOTICES_PER_PAGE,
$this->page,
'spam');
}
function showEmptyList()
{
// TRANS: Text displayed for public feed when there are no public notices.
$message = _('This is the timeline of spam messages for %%site.name%% but none have been detected yet.');
$this->elementStart('div', 'guide');
$this->raw(common_markup_to_html($message));
$this->elementEnd('div');
}
/**
* Return true if read only.
*
* MAY override
*
* @param array $args other arguments
*
* @return boolean is read only action?
*/
function isReadOnly($args)
{
return true;
}
}

View File

@ -0,0 +1,171 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2012, StatusNet, Inc.
*
* Spam filter class
*
* 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 Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2012 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);
}
/**
* Spam filter class
*
* Local proxy for remote filter
*
* @category Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2012 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class SpamFilter extends OAuthClient {
const HAM = 'ham';
const SPAM = 'spam';
public $server;
function __construct($server, $consumerKey, $secret) {
parent::__construct($consumerKey, $secret);
$this->server = $server;
}
protected function toActivity($notice) {
// FIXME: need this to autoload ActivityStreamsMediaLink
$doc = new ActivityStreamJSONDocument();
$activity = $notice->asActivity(null);
return $activity;
}
public function test($notice) {
$activity = $this->toActivity($notice);
return $this->testActivity($activity);
}
public function testActivity($activity) {
$response = $this->postJSON($this->server . "/is-this-spam", $activity->asArray());
$result = json_decode($response->getBody());
return $result;
}
public function train($notice, $category) {
$activity = $this->toActivity($notice);
return $this->trainActivity($activity, $category);
}
public function trainActivity($activity, $category) {
switch ($category) {
case self::HAM:
$endpoint = '/this-is-ham';
break;
case self::SPAM:
$endpoint = '/this-is-spam';
break;
default:
throw new Exception("Unknown category: " + $category);
}
$response = $this->postJSON($this->server . $endpoint, $activity->asArray());
// We don't do much with the results
return true;
}
public function trainOnError($notice, $category) {
$activity = $this->toActivity($notice);
return $this->trainActivityOnError($activity, $category);
}
public function trainActivityOnError($activity, $category) {
$result = $this->testActivity($activity);
if (($category === self::SPAM && $result->isSpam) ||
($category === self::HAM && !$result->isSpam)) {
return true;
} else {
return $this->trainActivity($activity, $category);
}
}
function postJSON($url, $body)
{
$request = OAuthRequest::from_consumer_and_token($this->consumer,
$this->token,
'POST',
$url);
$request->sign_request($this->sha1_method,
$this->consumer,
$this->token);
$hclient = new HTTPClient($url);
$hclient->setConfig(array('connect_timeout' => 120,
'timeout' => 120,
'follow_redirects' => true,
'ssl_verify_peer' => false,
'ssl_verify_host' => false));
$hclient->setMethod(HTTP_Request2::METHOD_POST);
$hclient->setBody(json_encode($body));
$hclient->setHeader('Content-Type', 'application/json');
$hclient->setHeader($request->to_header());
// Twitter is strict about accepting invalid "Expect" headers
// No reason not to clear it still here -ESP
$hclient->setHeader('Expect', '');
try {
$response = $hclient->send();
$code = $response->getStatus();
if (!$response->isOK()) {
throw new OAuthClientException($response->getBody(), $code);
}
return $response;
} catch (Exception $e) {
throw new OAuthClientException($e->getMessage(), $e->getCode());
}
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2012, StatusNet, Inc.
*
* Spam notice stream
*
* 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 Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2012 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);
}
/**
* Spam notice stream
*
* @category Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2012 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class SpamNoticeStream extends ScopingNoticeStream
{
function __construct($tag, $profile = -1)
{
if (is_int($profile) && $profile == -1) {
$profile = Profile::current();
}
parent::__construct(new CachingNoticeStream(new RawSpamNoticeStream(),
'spam_score:notice_ids'));
}
}
/**
* Raw stream of spammy notices
*
* @category Stream
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class RawSpamNoticeStream extends NoticeStream
{
function getNoticeIds($offset, $limit, $since_id, $max_id)
{
$ss = new Spam_score();
$ss->is_spam = 1;
$ss->selectAdd();
$ss->selectAdd('notice_id');
Notice::addWhereSinceId($ss, $since_id, 'notice_id');
Notice::addWhereMaxId($ss, $max_id, 'notice_id');
$ss->orderBy('notice_created DESC, notice_id DESC');
if (!is_null($offset)) {
$ss->limit($offset, $limit);
}
$ids = array();
if ($ss->find()) {
while ($ss->fetch()) {
$ids[] = $ss->notice_id;
}
}
return $ids;
}
}

View File

@ -0,0 +1,155 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2012, StatusNet, Inc.
*
* Train a notice as spam
*
* 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 Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2012 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);
}
/**
* Train a notice as spam
*
* @category Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2012 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class TrainAction extends Action
{
protected $notice = null;
protected $filter = null;
protected $category = null;
/**
* For initializing members of the class.
*
* @param array $argarray misc. arguments
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
// User must be logged in.
$user = common_current_user();
if (empty($user)) {
throw new ClientException(_("You must be logged in to train spam."), 403);
}
// User must have the right to review spam
if (!$user->hasRight(ActivitySpamPlugin::TRAINSPAM)) {
throw new ClientException(_('You cannot review spam on this site.'), 403);
}
$id = $this->trimmed('notice');
$this->notice = Notice::staticGet('id', $id);
if (empty($this->notice)) {
throw new ClientException(_("No such notice."));
}
$this->checkSessionToken();
$filter = null;
Event::handle('GetSpamFilter', array(&$filter));
if (empty($filter)) {
throw new ServerException(_("No spam filter configured."));
}
$this->filter = $filter;
$this->category = $this->trimmed('category');
if ($this->category !== SpamFilter::SPAM &&
$this->category !== SpamFilter::HAM)
{
throw new ClientException(_("No such category."));
}
return true;
}
/**
* Handler method
*
* @param array $argarray is ignored since it's now passed in in prepare()
*
* @return void
*/
function handle($argarray=null)
{
// Train
$this->filter->trainOnError($this->notice, $this->category);
// Re-test
$result = $this->filter->test($this->notice);
// Update or insert
$score = Spam_score::save($this->notice, $result);
// Show new toggle form
if ($this->category === SpamFilter::SPAM) {
$form = new TrainHamForm($this, $this->notice);
} else {
$form = new TrainSpamForm($this, $this->notice);
}
if ($this->boolean('ajax')) {
$this->startHTML('text/xml;charset=utf-8');
$this->elementStart('head');
// TRANS: Page title for page on which favorite notices can be unfavourited.
$this->element('title', null, _('Disfavor favorite.'));
$this->elementEnd('head');
$this->elementStart('body');
$form->show();
$this->elementEnd('body');
$this->elementEnd('html');
} else {
common_redirect(common_local_url('spam'), 303);
}
}
}

View File

@ -0,0 +1,146 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Toggle indicating spam, click to train as ham
*
* 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 Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 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);
}
/**
* Form
*
* @category Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class TrainHamForm extends Form {
var $notice = null;
function __construct($out, $notice) {
parent::__construct($out);
$this->notice = $notice;
}
/**
* Name of the form
*
* Sub-classes should overload this with the name of their form.
*
* @return void
*/
function formLegend()
{
return _("Train ham");
}
/**
* Visible or invisible data elements
*
* Display the form fields that make up the data of the form.
* Sub-classes should overload this to show their data.
*
* @return void
*/
function formData()
{
$this->hidden('notice', $this->notice->id);
}
/**
* Buttons for form actions
*
* Submit and cancel buttons (or whatever)
* Sub-classes should overload this to show their own buttons.
*
* @return void
*/
function formActions()
{
$this->submit('train-ham-submit-' . $this->notice->id,
_('Clear spam'),
'submit',
null,
_("Clear spam"));
}
/**
* ID of the form
*
* Should be unique on the page. Sub-classes should overload this
* to show their own IDs.
*
* @return int ID of the form
*/
function id()
{
return 'train-ham-' . $this->notice->id;
}
/**
* Action of the form.
*
* URL to post to. Should be overloaded by subclasses to give
* somewhere to post to.
*
* @return string URL to post to
*/
function action()
{
return common_local_url('train', array('category' => 'ham'));
}
/**
* Class of the form. May include space-separated list of multiple classes.
*
* If 'ajax' is included, the form will automatically be submitted with
* an 'ajax=1' parameter added, and the resulting form or error message
* will replace the form after submission.
*
* It's up to you to make sure that the target action supports this!
*
* @return string the form's class
*/
function formClass()
{
return 'form-train-ham ajax';
}
}

View File

@ -0,0 +1,146 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Toggle indicating ham, click to train as spam
*
* 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 Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 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);
}
/**
* Form
*
* @category Spam
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2011 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class TrainSpamForm extends Form {
var $notice = null;
function __construct($out, $notice) {
parent::__construct($out);
$this->notice = $notice;
}
/**
* Name of the form
*
* Sub-classes should overload this with the name of their form.
*
* @return void
*/
function formLegend()
{
return _("Train spam");
}
/**
* Visible or invisible data elements
*
* Display the form fields that make up the data of the form.
* Sub-classes should overload this to show their data.
*
* @return void
*/
function formData()
{
$this->hidden('notice', $this->notice->id);
}
/**
* Buttons for form actions
*
* Submit and cancel buttons (or whatever)
* Sub-classes should overload this to show their own buttons.
*
* @return void
*/
function formActions()
{
$this->submit('train-spam-submit-' . $this->notice->id,
_('Train spam'),
'submit',
null,
_("Mark as spam"));
}
/**
* ID of the form
*
* Should be unique on the page. Sub-classes should overload this
* to show their own IDs.
*
* @return int ID of the form
*/
function id()
{
return 'train-spam-' . $this->notice->id;
}
/**
* Action of the form.
*
* URL to post to. Should be overloaded by subclasses to give
* somewhere to post to.
*
* @return string URL to post to
*/
function action()
{
return common_local_url('train', array('category' => 'spam'));
}
/**
* Class of the form. May include space-separated list of multiple classes.
*
* If 'ajax' is included, the form will automatically be submitted with
* an 'ajax=1' parameter added, and the resulting form or error message
* will replace the form after submission.
*
* It's up to you to make sure that the target action supports this!
*
* @return string the form's class
*/
function formClass()
{
return 'form-train-spam ajax';
}
}