implement filters

master
Benjamin 'blindCoder' Schieder 2017-04-22 20:49:49 +02:00
parent a43f9862bb
commit 647d913dba
15 changed files with 356 additions and 17 deletions

View File

@ -35,9 +35,9 @@ sub create {
my %data = @_;
Tweetodon::DB->doINSERThash($class->dbTable, %data);
my $data = Tweetodon::DB->doSELECT("SELECT * FROM ".$class->dbTable." WHERE id = LAST_INSERT_ID()");
my $data = Tweetodon::DB->doSELECT("SELECT * FROM ".$class->dbTable." WHERE ID = LAST_INSERT_ID()");
$data = $$data[0];
return $class->get_by("id", $$data{id});
return $class->get_by("ID", $$data{ID});
}
sub new {
my $this = shift;
@ -55,10 +55,15 @@ sub save {
my $self = shift;
if (exists($self->{"data"}->{"password"}) && "x".$self->{"data"}->{"password"} ne "x"){
$self->{"data"}->{"password"} = md5_hex($self->{"data"}->{"id"}.$self->{"data"}->{"password"});
$self->{"data"}->{"password"} = md5_hex($self->{"data"}->{"ID"}.$self->{"data"}->{"password"});
} else {
delete $self->{"data"}->{"password"};
}
return Tweetodon::DB->doUPDATEhash($self->dbTable, "id = ".$self->{"data"}->{"id"}, %{$self->{"data"}});
return Tweetodon::DB->doUPDATEhash($self->dbTable, "ID = ".$self->{"data"}->{"ID"}, %{$self->{"data"}});
}
sub delete {
my $self = shift;
return Tweetodon::DB->doDELETE("DELETE FROM ".$self->dbTable." WHERE ID = ?", $self->{data}->{ID});
}
1;

View File

@ -47,14 +47,15 @@ sub doINSERThash {
my $sth = $DBH->prepare($SQL);
if ($DBI::err) {
&main::Error("SQL Error", "doINSERThash prepare failed:\n'".$DBI::errstr."'\nSQL was: $SQL");
return 1;
return 0;
}
$sth->execute(@RESTARGS);
if ($DBI::err) {
&main::Error("SQL Error", "doINSERThash execute failed:'".$DBI::errstr."'\nSQL was: $SQL\nRestargs:".Dumper(@RESTARGS));
return 1;
return 0;
}
$sth->finish();
return 1;
}
sub doSELECT {
shift;
@ -90,15 +91,15 @@ sub doDELETE {
my $sth = $DBH->prepare($SQL);
if ($DBI::err) {
&main::Error("SQL Error", "doDELETE prepare failed:\n'".$DBI::errstr."'\nSQL was: $SQL");
return 1;
return 0;
}
$sth->execute(@RESTARGS);
if ($DBI::err) {
&main::Error("SQL Error", "doDELETE execute failed:'".$DBI::errstr."'\nSQL was: $SQL\nRestargs:".Dumper(@RESTARGS));
return 1;
return 0;
}
$sth->finish();
return 0;
return 1;
}
sub doUPDATE {
my $this = shift;
@ -108,15 +109,15 @@ sub doUPDATE {
my $sth = $DBH->prepare($SQL);
if ($DBI::err) {
&main::Error("SQL Error", "doUPDATE prepare failed:\n'".$DBI::errstr."'\nSQL was: $SQL");
return 1;
return 0;
}
$sth->execute(@RESTARGS);
if ($DBI::err) {
&main::Error("SQL Error", "doUPDATE execute failed:'".$DBI::errstr."'\nSQL was: $SQL\nRestargs:".Dumper(@RESTARGS));
return 1;
return 0;
}
$sth->finish();
return 0;
return 1;
}
sub doUPDATEhash {
my $this = shift;
@ -136,14 +137,15 @@ sub doUPDATEhash {
my $sth = $DBH->prepare($SQL);
if ($DBI::err) {
&main::Error("SQL Error", "doUPDATEhash prepare failed:\n'".$DBI::errstr."'\nSQL was: $SQL");
return 1;
return 0;
}
$sth->execute(@RESTARGS);
if ($DBI::err) {
&main::Error("SQL Error", "doUPDATEhash execute failed:'".$DBI::errstr."'\nSQL was: $SQL\nRestargs:".Dumper(@RESTARGS));
return 1;
return 0;
}
$sth->finish();
return 1;
}
sub DBInit {

View File

@ -6,6 +6,7 @@ package Tweetodon::Feed;
@Tweetodon::Feed::ISA = qw(Tweetodon::Base);
use JSON;
use Data::Dumper;
use Tweetodon::Filter;
sub dbTable :lvalue { "feeds"; }
sub orderBy :lvalue { "url ASC"; }
@ -27,5 +28,15 @@ sub get_by_user_instance {
}
# Object methods
sub filters {
my $self = shift;
my $filters = Tweetodon::DB->doSELECT("SELECT * FROM filters WHERE feed_id = ? ORDER BY ID ASC", $self->{data}->{ID});
my @retVal;
foreach my $r (@$filters){
push @retVal, Tweetodon::Filter->new($r);
}
return @retVal;
}
1;

View File

@ -0,0 +1,45 @@
# vim: set foldmarker={,}:
use strict;
use Tweetodon::Base;
package Tweetodon::Filter;
@Tweetodon::Filter::ISA = qw(Tweetodon::Base);
use JSON;
use Data::Dumper;
use URI::Escape;
sub dbTable :lvalue { "filters"; }
sub orderBy :lvalue { "ID ASC"; }
# Class functions
# Object methods
sub apply {
my $self = shift;
my $entry = shift;
my $match = 1;
my $arg = $self->{data}->{field};
if ($arg eq "content"){
$arg = $entry->content()->body;
} else {
$arg = $entry->$arg();
}
my $regex = uri_unescape($self->{data}->{regex});
$regex =~ s,\\\\,\\,g;
if ($self->{data}->{match} eq "positive"){
if ($arg =~ /$regex/i){
return 1;
}
return 0;
} else {
if ($arg !~ /$regex/i){
return 1;
}
return 0;
}
return 0;
}
1;

View File

@ -54,7 +54,7 @@ sub render {
}
}
foreach ($self->{"set_cookie"}){
print "Set-Cookie: $_\n";
print "Set-Cookie: $_\n" if defined($_);
}
print "\n";

View File

@ -0,0 +1,121 @@
#!/usr/bin/perl -w
# vim: set foldmarker={,}:
use strict;
use HTML::Template;
use Tweetodon::DB;
use Tweetodon::Feed;
use Tweetodon::Filter;
use Tweetodon::Website;
package Tweetodon::Website::EditFeed;
use Data::Dumper;
use XML::Feed;
use URI;
@Tweetodon::Website::EditFeed::ISA = qw(Tweetodon::Website);
sub requires_authentication {
return 1;
}
sub fill_content {
my $class = shift;
my $output = shift;
my $feed = Tweetodon::Feed->get_by("ID", $main::FORM{id});
unless ($feed){
main::Error("Unknown feed", "This feed id is not known");
return 1;
}
if ($feed->{data}->{username} ne $main::CURRENTUSER->{data}->{acct} or $feed->{data}->{instance} ne $main::FORM{instance}){
main::Error("Unknown feed", "This feed id is not known");
return 1;
}
if ($main::FORM{action} and "x".$main::FORM{action} eq "xsave"){
my @filters = $feed->filters();
FILTER: foreach my $filter (@filters){
if ($main::FORM{"delete_".$filter->{data}->{ID}}){
$filter->delete();
next FILTER;
}
foreach my $key (keys(%{$filter->{data}})){
if ($key ne "ID" and $main::FORM{$key."_".$filter->{data}->{ID}}){
$filter->{data}->{$key} = $main::FORM{$key."_".$filter->{data}->{ID}};
}
}
$filter->save();
}
foreach my $key (grep(/^field_new/, keys(%main::FORM))){
$key =~ /^field_new([1-9][0-9]*)$/;
my $id = $1;
my %newfilter;
$newfilter{feed_id} = $main::FORM{id};
$newfilter{field} = $main::FORM{"field_new".$id};
$newfilter{regex} = $main::FORM{"regex_new".$id};
$newfilter{type} = $main::FORM{"type_new".$id};
$newfilter{match} = $main::FORM{"match_new".$id};
my $nf = Tweetodon::Filter->create(%newfilter);
}
}
$XML::Feed::MULTIPLE_ENCLOSURES = 1;
my $feeddata = XML::Feed->parse(URI->new($feed->{data}->{url}));
my @param_entries;
my @filters = $feed->filters();
foreach my $entry ($feeddata->items){
my %entry;
$entry{title} = $entry->title();
$entry{link} = $entry->link();
$entry{content} = $entry->content()->body;
$entry{author} = $entry->author();
$entry{issued} = $entry->issued();
$entry{modified} = $entry->modified();
$entry{id} = $entry->id();
$entry{tags} = join(", ", $entry->tags());
$entry{class} = "green";
foreach my $filter (@filters){
if ($filter->apply($entry)){
if ($filter->{data}->{type} eq "white"){
$entry{class} = "green";
} else {
$entry{class} = "red";
}
} else {
if ($filter->{data}->{type} eq "white"){
$entry{class} = "red";
} else {
$entry{class} = "green";
}
}
}
push @param_entries, \%entry;
}
$output->param("ENTRIES", \@param_entries);
my @param_filters;
foreach my $filter (@filters){
my %filter;
$filter{ID} = $filter->{data}->{ID};
$filter{regex} = $filter->{data}->{regex};
$filter{field} = $filter->{data}->{field};
$filter{type} = $filter->{data}->{type};
$filter{match} = $filter->{data}->{match};
push @param_filters, \%filter;
}
$output->param("FILTERS", \@param_filters);
$output->param("url", $feed->{data}->{url});
$output->param("feed_id", $feed->{data}->{ID});
return 1;
}
sub prerender {
my $self = shift;
$self->{"template"} = "EditFeed";
$self->{"content_type"} = "html";
$self->{"params"}->{"currentmode"} = "EditFeed";
}
1;

View File

@ -47,6 +47,7 @@ sub Error {{{
sub populateAddToFORM {{{
my $key = shift;
my $value = shift;
return unless defined($value);
$key =~ s/\+/ /g;
$key = uri_unescape($key);
$key =~ s/\[\]$//;
@ -119,7 +120,7 @@ my $object;
# TODO: This is a very bad solution but not as bad as an uncontrolled eval...
# The @main::modules array holds a list of all permissible values of the $main::FORM{"mode"} variable.
# If the value is not in this array, the request is not processed and an error is displayed.
my @modules = ("Login", "OAuthLogin", "Dashboard", "Callback", "JSON");
my @modules = ("Login", "OAuthLogin", "Dashboard", "Callback", "JSON", "EditFeed");
if (! grep {$_ eq $FORM{mode}} @modules) {
Error("Validation Error", "$FORM{mode} is not a valid module");

View File

@ -0,0 +1,6 @@
.green {
background-color: #80FF80;
}
.red {
background-color: #FF8080;
}

View File

View File

@ -59,9 +59,13 @@ body {
padding-right: 20px;
padding-left: 20px;
}
.onlywhenactive {
visibility: hidden;
}
.nav-sidebar > .active > a,
.nav-sidebar > .active > a:hover,
.nav-sidebar > .active > a:focus {
visibility: visible !important;
color: #fff;
background-color: #428bca;
}

View File

@ -0,0 +1,70 @@
var newnum = 0;
function appendFilter(id, field, regex, type, match){
console.log("id: "+id+" field: "+field+" regex: "+regex+" type: "+type);
var t = $("div#filters table tbody");
var a = "<tr>";
a += "<td>";
a += "<select name='field_"+id+"' id='field_"+id+"'>";
a += "<option disabled selected value='0'>Please select</option>";
a += "<option disabled value='0'>-------------</option>";
a += "<option value='id'>ID</option>";
a += "<option value='title'>Title</option>";
a += "<option value='link'>Link</option>";
a += "<option value='content'>Content</option>";
a += "<option value='author'>Author</option>";
a += "<option value='issued'>Issued</option>";
a += "<option value='modified'>Modified</option>";
a += "<option value='tags'>Tags</option>";
a += "</select>";
a += "</td>";
a += "<td>";
a += "<select name='match_"+id+"' id='match_"+id+"'>";
a += "<option value='positive'>Matches</option>";
a += "<option value='negative'>Does not match</option>";
a += "</select>";
a += "</td>";
a += "<td><input type='text' placeholder='Match.*?some.*regex' name='regex_"+id+"' id='regex_"+id+"'></td>";
a += "<td>";
a += "<select name='type_"+id+"' id='type_"+id+"'>";
a += "<option disabled selected value='0'>Please select</option>";
a += "<option disabled value='0'>-------------</option>";
a += "<option value='white'>Whitelist</option>";
a += "<option value='black'>Blacklist</option>";
a += "</select>";
a += "</td>";
if (!(""+id).match(/^new/)){
a += "<td><input type='checkbox' name='delete_"+id+"'></td>";
}
a += "</tr>";
t.append(a);
if (field){
t.find("#field_"+id).val(field);
}
if (regex){
t.find("#regex_"+id).val(regex);
}
if (type){
t.find("#type_"+id).val(type);
}
if (match){
t.find("#match_"+id).val(match);
}
}
function TweetodonOnReady(){
$("#rawentries").hide();
$("a#togglerawentries").on("click", function(){
$("#rawentries").toggle();
});
$("a#addfilter").on("click", function(){
newnum++;
appendFilter("new"+newnum);
});
$("a#savefilters").one("click", function(){
document.forms.form_filters.submit();
});
for (i=0; i<filters.length; i++){
appendFilter(filters[i].ID, filters[i].field, filters[i].regex, filters[i].type, filters[i].match);
}
}

View File

@ -48,6 +48,8 @@
<tr>
<td><TMPL_VAR NAME="count"></td>
<td><TMPL_VAR NAME="url"></td>
<td><a class="btn btn-default" href="index.pl?mode=EditFeed&id=<TMPL_VAR NAME="ID">">Edit</a></td>
<td><a class="btn btn-danger">Delete</a></td>
</tr>
</TMPL_LOOP>
</tbody>

View File

@ -0,0 +1,72 @@
<TMPL_INCLUDE NAME='_header.html'>
<TMPL_INCLUDE NAME='_navbar.html'>
<div class="container-fluid">
<div class="row">
<TMPL_INCLUDE NAME='_sidebar.html'>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h2 class="sub-header"><TMPL_VAR NAME="url"></h2>
<a id="togglerawentries" class="btn btn-info">Show raw entries</a><br >
<div id="rawentries" class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Link</th>
<th>Content</th>
<th>Author</th>
<th>Issued</th>
<th>Modified</th>
<th>Tags</th>
</tr>
</thead>
<tbody>
<TMPL_LOOP NAME="ENTRIES">
<tr class="<TMPL_VAR NAME="class">">
<td><TMPL_VAR NAME="id"></td>
<td><TMPL_VAR NAME="title"></td>
<td><TMPL_VAR NAME="link"></td>
<td><TMPL_VAR NAME="content"></td>
<td><TMPL_VAR NAME="author"></td>
<td><TMPL_VAR NAME="issued"></td>
<td><TMPL_VAR NAME="modified"></td>
<td><TMPL_VAR NAME="tags"></td>
</tr>
</TMPL_LOOP>
</tbody>
</table>
</div>
<form id="form_filters" method="POST">
<input type="hidden" name="action" value="save">
<h3>Filters</h3>
<div id="filters" class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Field</th>
<th>Matches</th>
<th>Regex</th>
<th>Type</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<a id="savefilters" class="btn btn-default" href="#">Save filters</a> <a id="addfilter" class="btn btn-danger">Add filter</a>
</div>
</form>
</div>
</div>
</div>
<script type="text/javascript">
var filters = [];
<TMPL_LOOP NAME="FILTERS">
filters.push({ID: <TMPL_VAR NAME="ID">, field: "<TMPL_VAR NAME="field">", regex: decodeURIComponent("<TMPL_VAR ESCAPE=URL NAME="regex">"), type: "<TMPL_VAR NAME="type">", match: "<TMPL_VAR NAME="match">"});
</TMPL_LOOP>
</script>
<TMPL_INCLUDE NAME='_footer.html'>

View File

@ -1,5 +1,6 @@
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li id="Dashboard"><a href="index.pl?mode=Dashboard">Dashboard</a></li>
<li class="onlywhenactive" id="EditFeed"><a href="index.pl?mode=EditFeed">Edit Feed</a></li>
</ul>
</div><!-- /sidebar -->

View File

@ -1,5 +1,4 @@
#!/bin/bash
echo curl -X GET --header "Authorization: Bearer ${1}" "${2}/api/v1/accounts/verify_credentials" >&2
curl -X GET --header "Authorization: Bearer ${1}" "${2}/api/v1/accounts/verify_credentials" 2>/dev/null
#https://cloud.digitalocean.com/v1/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL