Merge ActivitySpam plugin
This commit is contained in:
commit
7a9777df05
318
plugins/ActivitySpam/ActivitySpamPlugin.php
Normal file
318
plugins/ActivitySpam/ActivitySpamPlugin.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
216
plugins/ActivitySpam/Spam_score.php
Normal file
216
plugins/ActivitySpam/Spam_score.php
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
BIN
plugins/ActivitySpam/icons/bullet_black.png
Normal file
BIN
plugins/ActivitySpam/icons/bullet_black.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 211 B |
BIN
plugins/ActivitySpam/icons/exclamation.png
Normal file
BIN
plugins/ActivitySpam/icons/exclamation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 701 B |
105
plugins/ActivitySpam/scripts/testuser.php
Normal file
105
plugins/ActivitySpam/scripts/testuser.php
Normal 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);
|
||||||
|
}
|
81
plugins/ActivitySpam/scripts/trainuser.php
Normal file
81
plugins/ActivitySpam/scripts/trainuser.php
Normal 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);
|
||||||
|
}
|
165
plugins/ActivitySpam/spam.php
Normal file
165
plugins/ActivitySpam/spam.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
171
plugins/ActivitySpam/spamfilter.php
Normal file
171
plugins/ActivitySpam/spamfilter.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
plugins/ActivitySpam/spamnoticestream.php
Normal file
101
plugins/ActivitySpam/spamnoticestream.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
155
plugins/ActivitySpam/train.php
Normal file
155
plugins/ActivitySpam/train.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
plugins/ActivitySpam/trainhamform.php
Normal file
146
plugins/ActivitySpam/trainhamform.php
Normal 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';
|
||||||
|
}
|
||||||
|
}
|
146
plugins/ActivitySpam/trainspamform.php
Normal file
146
plugins/ActivitySpam/trainspamform.php
Normal 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';
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user