commit bf921de540211122c89ed0c14aba5adc05fa9e6f Author: briandilley Date: Thu Oct 14 22:20:03 2004 +0000 Initial revision diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..cfc790e --- /dev/null +++ b/Changelog @@ -0,0 +1,5 @@ +10-10-2004 + * Added kwote_backup table. + * Implemented functionality to remove nagative kwotes. + + diff --git a/html/content-addform-thanks.html b/html/content-addform-thanks.html new file mode 100644 index 0000000..850f119 --- /dev/null +++ b/html/content-addform-thanks.html @@ -0,0 +1,2 @@ + +Thank you for adding your kwote, here's a link to it: #${KWOTE_ID} diff --git a/html/content-addform.html b/html/content-addform.html new file mode 100644 index 0000000..d6590e0 --- /dev/null +++ b/html/content-addform.html @@ -0,0 +1,7 @@ +
+
+ +
+ +
+
diff --git a/html/content-default.html b/html/content-default.html new file mode 100644 index 0000000..07cc40d --- /dev/null +++ b/html/content-default.html @@ -0,0 +1,40 @@ + + +
+

Welcome

+ We need to add some content here, soon! Anyway, this is kwotes. It's + a chat quote database system, with a twist. This chat quote database + is publicly moderated... so ALL of your submissions are accepted, and + it's up to the general public to determine wether or not it's funny... + no stupid moderators who wouldn't know funny if it grabed them by the + asshole and ripped them apart. Anyway, this system has some advantages + over existing systems, here are a few: + + + + We're thinking about creating an egdrop script for auto submiting + quotes to the system, we'll see how that goes :) + +
+ diff --git a/html/content-error.html b/html/content-error.html new file mode 100644 index 0000000..b225e67 --- /dev/null +++ b/html/content-error.html @@ -0,0 +1,3 @@ + +

${ERROR_MESSAGE}

+ diff --git a/html/content-list-navigate-no-back.html b/html/content-list-navigate-no-back.html new file mode 100644 index 0000000..047826b --- /dev/null +++ b/html/content-list-navigate-no-back.html @@ -0,0 +1,4 @@ + +
+ Next > +
diff --git a/html/content-list-navigate-no-forward.html b/html/content-list-navigate-no-forward.html new file mode 100644 index 0000000..700704c --- /dev/null +++ b/html/content-list-navigate-no-forward.html @@ -0,0 +1,4 @@ +
+ < Previous +
+ diff --git a/html/content-list-navigate.html b/html/content-list-navigate.html new file mode 100644 index 0000000..025688f --- /dev/null +++ b/html/content-list-navigate.html @@ -0,0 +1,6 @@ +
+ < Previous +   |   + Next > +
+ diff --git a/html/content-search.html b/html/content-search.html new file mode 100644 index 0000000..eec9eb7 --- /dev/null +++ b/html/content-search.html @@ -0,0 +1,28 @@ + +
+
+ + + + Search:   +    + + Kwotes per page: + +    + + Sort by: + +
+
diff --git a/html/content-show-kwote.html b/html/content-show-kwote.html new file mode 100644 index 0000000..f6f66e7 --- /dev/null +++ b/html/content-show-kwote.html @@ -0,0 +1,13 @@ +
+
+ #${KWOTE_ID} + + - +  ${KWOTE_RATING}  + + + +
+
+ ${KWOTE_TEXT} +
+
diff --git a/html/footer.html b/html/footer.html new file mode 100644 index 0000000..7a3ecf1 --- /dev/null +++ b/html/footer.html @@ -0,0 +1,12 @@ + + + + + + diff --git a/html/header.html b/html/header.html new file mode 100644 index 0000000..cd0a282 --- /dev/null +++ b/html/header.html @@ -0,0 +1,59 @@ + + + + + + ${TITLE} + + + + + + + +
+ Kwotes.org - The publicly moderated chat quote database + + + +
+
+ Kwote # + + +
+
+ +
diff --git a/html/quotes-ie.css b/html/quotes-ie.css new file mode 100644 index 0000000..b0bbbc7 --- /dev/null +++ b/html/quotes-ie.css @@ -0,0 +1,11 @@ +/* Stupid IE stuff... at least they get to see something hilite */ +.tab a:active +{ + background-color: #8b8bca; +} + +.tab a:hover +{ + background-color: #dfdff0; +} +/* End stupid IE stuff */ diff --git a/html/quotes.css b/html/quotes.css new file mode 100644 index 0000000..2bb0541 --- /dev/null +++ b/html/quotes.css @@ -0,0 +1,122 @@ +body +{ + font-family: Trebuchet MS, Tahoma, Arial, Sans-Serif; + margin: 0px; +} + +a:link +{ + text-decoration: none; +} + +a:visited +{ + text-decoration: none; +} + +a:hover, a:active +{ + text-decoration: underlined; +} + +/* Begin Containers */ +.container +{ + width: 700px; + position: relative; + margin-left: auto; + margin-right: auto; + text-align: left; + padding: 2px 2px 5px 2px; +} + +.body-container +{ + position: relative; + margin-top: 5px; + margin-bottom: 5px; +} +/* End Containers */ + +.direct-form +{ + position: absolute; + top: 1px; + right: 4px; + font-size: 0.8em; +} + +.direct-form input +{ + background-color: #dfdff0; + border: 1px solid black; +} + +.quote +{ + position: relative; + margin-bottom: 15px; +} + +.quote-header +{ + position: relative; + border-bottom: 1px solid black; + font-size: 0.8em; +} + +.vote-controls +{ + position: absolute; + top: 0px; + right: 0px; +} + +.quote-content +{ + font-size: 0.9em; +} + +.footer +{ + font-size: 0.8em; + margin-top: 10px; +} + +/* Horizontal Menu */ +.tab-list +{ + margin: 0px; + padding: 0px; + position: relative; + list-style-type: none; + background-color: #bfbfe2; + height: 18; +} + +/* This is used by older browsers & IE */ +.tab-list .tab +{ + display: inline; + margin: 0px; + padding-left: 3px; + padding-right: 3px; + font-size: 0.7em; + height: 18px; +} + +/* Good browsers use this too */ +.tab-list .tab +{ + display: table-cell; +} + +.tab:active +{ + background-color: #8b8bca; +} + +.tab:hover +{ + background-color: #dfdff0; +} diff --git a/kwotes-lib.pl b/kwotes-lib.pl new file mode 100755 index 0000000..afb2755 --- /dev/null +++ b/kwotes-lib.pl @@ -0,0 +1,326 @@ +#!/usr/bin/perl + +# we use DBI, for it's sexy body +use DBI; + +# database connection +my $GLOBAL_DBH = undef; + +## +# Returns the kwote count +sub get_kwote_count { + + # connect + my $dbh = get_db_connection(); + + # execute + my $sth = $dbh->prepare("SELECT COUNT(*) as kwote_count FROM kwote"); + $sth->execute(); + + # return + my $row = $sth->fetchrow_hashref(); + return $row->{"kwote_count"}; +} + +## +# Returns the kwote_backup count +sub get_kwote_backup_count { + + # connect + my $dbh = get_db_connection(); + + # execute + my $sth = $dbh->prepare("SELECT COUNT(*) as kwote_count FROM kwote_backup"); + $sth->execute(); + + # return + my $row = $sth->fetchrow_hashref(); + return $row->{"kwote_count"}; +} + +## +# does some minor database cleanup +sub cleanup { + + # get a db connection + my $dbh = get_db_connection(); + + # backup kwotes to be deleted + my $sth = $dbh->prepare( + "INSERT INTO kwote_backup SELECT * FROM kwote WHERE ". + "(now()-submit_dt)>? AND rating<0" + ); + $sth->bind_param(1, NEGATIVE_KWOTE_TTL); + $sth->execute() or die "Couldn't backup kwotes"; + + # delete kwotes + $sth = $dbh->prepare( + "DELETE FROM kwote WHERE (now()-submit_dt)>? AND rating<0" + ); + $sth->bind_param(1, NEGATIVE_KWOTE_TTL); + $sth->execute() or die "Couldn't delete kwotes"; + + # delete the vote log (this doesn't affect kwote rating) + $sth = $dbh->prepare( + "DELETE FROM vote WHERE (now()-vote_dt)>?" + ); + $sth->bind_param(1, VOTE_TTL); + $sth->execute() or die "Couldn't delete votes"; + + # let em know we're good + print "Kwote Database cleanup complete\n"; + + # w00t + return 0; +} + +## +# votes on a kwote +sub vote { + my ($addr, $kid, $amt) = @_; + + # connect to db + my $dbh = get_db_connection(); + + # prepare statement + my $sth = $dbh->prepare( + "SELECT COUNT(*) as vote_count FROM vote WHERE ". + "ip_address=? AND kwote_id=?" + ); + $sth->bind_param(1, $addr); + $sth->bind_param(2, $kid); + + # execute + $sth->execute(); + + # get row + my $row = $sth->fetchrow_hashref(); + + # check if they suck + return undef if ($row->{"vote_count"}>=MAX_VOTES_PER_IP); + + # prepare + $sth = $dbh->prepare( + "UPDATE kwote SET rating=rating+(?) WHERE id=?" + ); + $sth->bind_param(1, $amt); + $sth->bind_param(2, $kid); + $sth->execute() or return undef; + + # record the vote + $sth = $dbh->prepare( + "INSERT INTO vote (ip_address, kwote_id, vote_dt) ". + "VALUES (?, ?, now())" + ); + $sth->bind_param(1, $addr); + $sth->bind_param(2, $kid); + $sth->execute() or return undef; + + # we're good + return 1; +} + +## +# adds a kwote to the database +sub add_kwote { + my ($dbh, $kwote_text, $ip_address) = @_; + my ($addr, $kid, $amt) = @_; + + # make sure the kwote is ok + return undef if (!defined($kwote_text) || $kwote_text eq ""); + + # prepare statement + my $sth = $dbh->prepare( + "SELECT COUNT(*) as kwote_count FROM kwote WHERE ip_address=? AND (now()-submit_dt)bind_param(1, $ip_address); + $sth->bind_param(2, SECS_BETWEEN_KWOTES); + + # execute + $sth->execute() or return undef; + + # get row + my $row = $sth->fetchrow_hashref() or return undef; + + # check if they suck + return undef if ($row->{"kwote_count"}>=MAX_KWOTES_PER_IP); + + # prepare statement + my $sth = $dbh->prepare( + "INSERT INTO kwote (submit_dt, content, rating, ip_address) ". + "VALUES (now(), ?, ?, ?)" + ) or return undef; + + # bind params + $sth->bind_param(1, $kwote_text); # this is the kwote text + $sth->bind_param(2, 0); # no rating as of yet + $sth->bind_param(3, $ip_address); # the ip address + + # execute + $sth->execute() or return undef; + + # return the id + return $dbh->{insertid}; +} + +## +# adds a kwote to the database +sub get_kwote { + my ($dbh, $kid) = @_; + + # prepare statement + my $sth = $dbh->prepare( + "SELECT * FROM kwote WHERE id=?" + ) or return undef; + + # bind params + $sth->bind_param(1, $kid); + + # execute + $sth->execute() or return undef; + + # get the row + my $row = $sth->fetchrow_hashref(); + + # return the id + return (defined($row)) ? $row : undef; +} + +## +# Gets a list of kwotes +sub list_kwotes { + my ($dbh, $sort_by, $order_direction, $return_amt, + $start_index, $search_string) = @_; + + # clean up the numbers + $return_amt =~ s/[^0-9]//ig; + $start_index =~ s/[^0-9]//ig; + + # ensure these numbers are ok + if ($start_index eq "" || int($start_index)<=0) { + $start_index = 0; + } + if ($return_amt eq "" || int($return_amt)<=0 + || int($return_amt) > 200) { + $return_amt = 20; + } + + # break out the keywords + my @kws = split(/,/,$search_string); + + # build SQL query + my $sql = "SELECT * FROM kwote "; + + # search stuff + if (defined($search_string)) { + $sql .= "WHERE 1=1 "; + foreach my $kw (@kws) { + $sql.= "AND content LIKE ? "; + } + } + + # sorting and paging + if (defined($sort_by)) { + $sql .= "ORDER BY $sort_by $order_direction "; + } + + # paging + $sql .= "LIMIT $start_index, $return_amt "; + + # prepare + my $sth = $dbh->prepare($sql) or return undef; + + # apply the search criteria + if (defined($search_string)) { + for ($i=0; $i<@kws; $i++) { + $sth->bind_param($i+1, "\%".$kws[$i]."\%"); + } + } + + + # execute + $sth->execute() or return undef; + + # get the rows + my @rows; + while (my $row = $sth->fetchrow_hashref()) { + push(@rows, $row); + } + + # return it + return @rows; +} + +## +# Connect to the database +sub get_db_connection { + if (!$GLOBAL_DBH) { + $GLOBAL_DBH = DBI->connect( + "dbi:".DB_TYPE.":".DB_NAME.":".DB_HOST, + DB_USER, + DB_PASS + ); + } + return $GLOBAL_DBH; +} + +## +# Escape html +sub html_escape { + my ($data) = @_; + $data =~ s//>/g; + $data =~ s/\n/
/g; + $data =~ s/"/"/g; + return $data; +} + +## +# Sends the HTML header +sub send_html_header { + print STDOUT "Content-type: text/html\n\n"; +} + +## +# Renders an HTML template +sub wrap_template { + my ($template_file, %vars) = @_; + open(IN,"$template_file"); + my $data = join("",); + close(IN); + foreach $key (keys %vars) { + $data =~ s/\${$key}/$vars{$key}/ig; + } + return $data; +} + +## +# Wraps and renders a template +sub render_template { + my ($template_file, %vars) = @_; + my $data = wrap_template($template_file, %vars); + print STDOUT $data; +} + +## +# Parse form data +sub parse_form { + my (@pairs, $pair, $buffer, %FORM); + if ($ENV{'REQUEST_METHOD'} eq 'GET') { + @pairs = split(/&/, $ENV{'QUERY_STRING'}); + } elsif ($ENV{'REQUEST_METHOD'} eq 'POST') { + read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); + @pairs = split(/&/, $buffer); + } + foreach $pair (@pairs) { + local($name, $value) = split(/=/, $pair); + $name =~ tr/+/ /; + $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; + $value =~ tr/+/ /; + $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; + $FORM{$name} = $value; + } + return %FORM; +} + +1; diff --git a/kwotes.conf.pl b/kwotes.conf.pl new file mode 100755 index 0000000..53147f7 --- /dev/null +++ b/kwotes.conf.pl @@ -0,0 +1,38 @@ +#!/usr/bin/perl + +use constant { + DB_TYPE => "mysql", # dbi database type (only MySQL is + # supported currently, due to the + # fact that "LIMIT X,X" is used + + DB_NAME => "kwotes", # database name + + DB_HOST => "localhost", # database host + + DB_USER => "kwotes", # database user + + DB_PASS => "kw0tes", # database password + + MAX_VOTES_PER_IP => 4, # maximum votes per ip address per + # VOTE_TTL seconds. + + MAX_KWOTES_PER_IP => 5, # maximum kwotes allowed per ip + # in SECS_BETWEEN_KWOTES + + SECS_BETWEEN_KWOTES => 60*60, # seconds a user must wait after + # submitting MAX_KWOTES_PER_IP + # kwotes to the system before they + # are allowed to submit another + # kwote + + NEGATIVE_KWOTE_TTL => (60*60)*24, # seconds before a negative rated + # quote is moved to the kwote + # backup table and deleted + + VOTE_TTL => (60*60)*24 # seconds a vot log lasts, the vote + # log is the mechanism that keeps + # people from voting over and over +}; + +1; + diff --git a/kwotes.pl b/kwotes.pl new file mode 100755 index 0000000..419f302 --- /dev/null +++ b/kwotes.pl @@ -0,0 +1,208 @@ +#!/usr/bin/perl + +################################################### +# -*- kwotes.pl -*- # +################################################### +# TOD: Put some interesting shit here, perhaps # +# something about kwotes being GPL # +################################################### + +# bring in the config +require "kwotes.conf.pl"; + +# bring in the required libs +require "kwotes-lib.pl"; + +# is this getting called by the "delete" cronjob? +if ($ARGV[0] eq "cleanup") { + exit cleanup(); +} + +# parse the form data +my %FORM = parse_form(); + +# some vars +my $action = $FORM{"action"}; +my $main_content; +my %vars; + +# populate %vars with ENV +foreach my $key (keys %ENV) { + $vars{$key} = $ENV{$key}; +} + +# send the HTML header +send_html_header(); + +# add information that is displayed on every page +$vars{KWOTE_COUNT} = get_kwote_count(); +$vars{KWOTE_BACKUP_COUNT} = get_kwote_backup_count(); + +############ +# action: add (show add form) +if ($action eq "add") { + $vars{TITLE} = "Add Kwote"; + $main_content = wrap_template("html/content-addform.html", %vars); + +############ +# action: doadd (add the kwote to the db) +} elsif ($action eq "doadd") { + + if ($FORM{"content"} eq "") { + $vars{TITLE} = "An Error Occured"; + $vars{ERROR_MESSAGE} = "No text entered"; + $main_content = wrap_template("html/content-error.html",%vars); + + } else { + + # add the kwote + my $dbh = get_db_connection(); + my $kid = add_kwote($dbh, $FORM{"content"}, $ENV{"REMOTE_ADDR"}); + + # wtf? errors? in my code? noooo. + if (!defined($kid)) { + $vars{TITLE} = "An Error Occured"; + $vars{ERROR_MESSAGE} = "Couldn't add kwote"; + $main_content = wrap_template("html/content-error.html",%vars); + + # all was good + } else { + $vars{TITLE} = "Kwote Added"; + $vars{KWOTE_ID} = $kid; + $main_content = wrap_template("html/content-addform-thanks.html", %vars); + + } + } + +########## +# action: show +} elsif ($action eq "show") { + + # get the kwote + my $dbh = get_db_connection(); + my $kwote = get_kwote($dbh, $FORM{"id"}); + + # wtf? errors? in my code? noooo. + if (!defined($kwote)) { + $vars{TITLE} = "Kwote Does Not Exist"; + $vars{ERROR_MESSAGE} = "That kwote does not exist"; + $main_content = wrap_template("html/content-error.html",%vars); + + # all was good + } else { + $vars{TITLE} = "Kwote \#$kwote->{'id'}"; + $vars{KWOTE_ID} = $kwote->{'id'}; + $vars{KWOTE_TEXT} = html_escape($kwote->{'content'}); + $vars{KWOTE_RATING} = $kwote->{'rating'}; + $main_content = wrap_template("html/content-show-kwote.html", %vars); + + } + +########## +# action: latest +} elsif ($action eq "list") { + + # what are we sorting on + my $sort = ($FORM{"o"} eq "date") ? + "submit_dt" : ( ($FORM{"o"} eq "rating") ? "rating" : undef); + + # get start index + my $start_index = (defined($FORM{"s"})) ? $FORM{"s"} : 0; + + # get max amount + my $max_returned = (defined($FORM{"m"})) ? $FORM{"m"} : 20; + + # what is the "max records" we'll consider? + my $max_records = (defined($FORM{"mr"})) ? $FORM{"mr"} : 9999999999; + + # what is the "sort order" + my $sort_order = ($FORM{"so"} eq "reverse") ? "ASC" : "DESC"; + + # search string? + my $search_string = $FORM{"ss"}; + + # get the kwote + my $dbh = get_db_connection(); + my @rows = list_kwotes($dbh, $sort, $sort_order, $max_returned, $start_index, $search_string); + + # setup these vars + $vars{TITLE} = "Kwotes"; + $vars{ORDER} = $FORM{"o"}; + $vars{NEXT_INDEX} = $start_index+$max_returned; + $vars{MAX_RETURN} = $max_returned; + $vars{LAST_INDEX} = $start_index-$max_returned; + $vars{MAX_RECORDS} = $max_records; + + # add the search header if it was a search + if (defined($search_string)) { + $main_content .= wrap_template("html/content-search.html", %vars); + } + + # get the navigation template + my $navigation_template = undef; + + # forward, no back + if ($start_index<=0 && @rows>=$max_returned + && ($start_index+$max_returned)<$max_records) { + $navigation_template = "html/content-list-navigate-no-back.html"; + + # forward and back + } elsif ($start_index>0 && @rows>=$max_returned + && ($start_index+$max_returned)<$max_records ) { + $navigation_template = "html/content-list-navigate.html"; + + # back only + } elsif ($start_index>0 && @rows<$max_returned) { + $navigation_template = "html/content-list-navigate-no-forward.html"; + } + + # wrap the navigation template + $main_content .= wrap_template($navigation_template, %vars); + + # loop through the results + if (defined(@rows)) { + for (my $i=0; $i<@rows && ($i+$start_index)<$max_records; $i++) { + my $row = $rows[$i]; + $vars{KWOTE_ID} = $row->{'id'}; + $vars{KWOTE_TEXT} = html_escape($row->{'content'}); + $vars{KWOTE_RATING} = $row->{'rating'}; + $main_content .= wrap_template("html/content-show-kwote.html", %vars); + } + + } + + # wrap the navigation template + $main_content .= wrap_template($navigation_template, %vars); + +########## +# action: search (show the search page) +} elsif ($action eq "search") { + $vars{TITLE} = "Search"; + $main_content = wrap_template("html/content-search.html", %vars); + +########## +# action: love +} elsif ($action eq "love") { + vote($ENV{"REMOTE_ADDR"}, $FORM{"kid"}, "1"); + $vars{TITLE} = "Love"; + $main_content = "Vote Counted"; + +########## +# action: hate +} elsif ($action eq "hate") { + vote($ENV{"REMOTE_ADDR"}, $FORM{"kid"}, "-1"); + $vars{TITLE} = "Hate"; + $main_content = "Vote Counted"; + +########## +# show the homepage +} else { + $vars{TITLE} = "The Better kwote Database"; + $main_content = wrap_template("html/content-default.html", %vars); + +} + +# finish the HTML +render_template("html/header.html", %vars); +print STDOUT $main_content; +render_template("html/footer.html", %vars); diff --git a/kwotes.sql b/kwotes.sql new file mode 100755 index 0000000..5e11b70 --- /dev/null +++ b/kwotes.sql @@ -0,0 +1,28 @@ + +CREATE TABLE kwote ( + id INTEGER AUTO_INCREMENT, + submit_dt DATETIME, + content TEXT, + rating INT, + ip_address VARCHAR(15), + PRIMARY KEY(id) +); + +CREATE TABLE kwote_backup ( + id INTEGER AUTO_INCREMENT, + submit_dt DATETIME, + content TEXT, + rating INT, + ip_address VARCHAR(15), + PRIMARY KEY(id) +); + +CREATE TABLE vote ( + id INT AUTO_INCREMENT, + ip_address VARCHAR(15), + kwote_id INT, + vote_dt DATETIME, + PRIMARY KEY(id), + FOREIGN KEY(kwote_id) REFERENCES kwote(id) +); +