App-news
view release on metacpan or search on metacpan
script/news view on Meta::CPAN
}
# reverse the list of articles, latest ones come first
return [reverse @$articles]});
return $c->render(template => 'noserver') if 'error' eq $data;
$nntp->quit if $nntp;
# If the cached data did not include our article, delete the cache and retry.
# This could be optimized to extend the existing dataâ¦
if ($include and (@$data == 0 or $include < $data->[$#$data]->{num})) {
my $seen = grep { $_->{num} == $include } @$data;
if (not $seen) {
app->log->debug("$include was not seen in the cached data");
$cache->set("$group tag $tag" => undef);
return $c->redirect_to('tag');
}
}
$c->render(template => 'tag', group => $group, tag => $tag, edit => $edit, list => $data);
} => 'tag';
# This only works for message-ids, not for message numbers (since they require a
# group).
get '/article/#id' => sub {
my $c = shift;
show_article($c, $c->param('id'));
} => 'article_id';
get '/article/#group/#id' => sub {
my $c = shift;
show_article($c, $c->param('id'), $c->param('group'));
} => 'article';
sub show_article {
# When following a link from the group, $id_or_num is a num and $group is
# important. When following a reference from an article, $id_or_num is a
# message-id and $group is only used for the reply form.
my ($c, $id_or_num, $group) = @_;
my $article = cached("$group article $id_or_num", sub {
my $nntp = Net::NNTP->new() or return 'noserver';
$nntp->group($group) if $group;
my $article = $nntp->article($id_or_num);
return 'unknown' unless $article;
# app->log->trace(join("", @$article));
# $article is header lines, an empty line, and body lines
my $headers = Mojo::Headers->new;
while ($_ = shift(@$article)) {
$headers->parse("$_\r\n");
last unless /\S/;
}
my $id = $headers->header("message-id");
my $subject = decode("MIME-Header", $headers->header("subject")) || "?";
my $from = no_email(decode("MIME-Header", $headers->header("from")));
my $date = $headers->header("date");
my $dt = DateTime::Format::Mail->new->loose->parse_datetime($date);
$date = [$dt->ymd, sprintf("%02d:%02d", $dt->hour, $dt->minute)];
my $newsgroups = [split(/\s*,\s*/, decode("MIME-Header", $headers->header("newsgroups")) || "")];
$group ||= "@$newsgroups";
my $references = [split(/\s+/, decode("MIME-Header", $headers->header("references")) || "")];
my $body = join("", @$article);
$body =~ s/\s*<\S*?@\S*?>//g; # remove email addresses
$body =~ s/\s*"\S*?@\S*?"//g; # remove email addresses
if ($headers->header('content-type')) {
my ($charset) = $headers->header('content-type') =~ /charset=['"]?([^;'"]*)/;
$body = decode($charset, $body) if $charset;
}
my $value = {
id => $id,
group => $group,
from => $from,
subject => $subject,
date => $date,
newsgroups => $newsgroups,
references => $references,
html_body => html_unwrap($body),
body => $body,
};
# perhaps we have cached replies from looking at the group (space and no page number at the end)
my $cached_group = cached("$group list ", sub {}) || {};
my $cached_article = (first { $_->{id} eq $id } @{$cached_group->{articles}}) || {};
$value->{replies} = $cached_article->{replies} || [];
app->log->debug("$id replies: @{$value->{replies}}");
$nntp->quit;
# If $id_or_num was a number, add a second key to the cache in case we need
# the same article but following a reference.
$cache->set("$group article $id" => [time, $value]) if $id_or_num ne $id;
return $value });
return $c->render(template => $article) unless ref $article;
$c->render(template => 'article', article => $article, edit => $c->param('edit'));
}
get '/post/#group' => sub {
my $c = shift;
# copy from the cookie
$c->param($_ => $c->session->{$_}) for qw(name username password);
$c->render(template => 'post',
id => '',
subject => '',
supersedes => '',
references => '');
} => 'new';
post '/reply' => sub {
my $c = shift;
# copy from the cookie
$c->param($_ => $c->session->{$_}) for qw(name username password);
$c->render(template => 'post',
id => $c->param('id'),
group => $c->param('group'),
subject => $c->param('subject'),
supersedes => '',
references => $c->param('references'));
} => 'reply';
post '/supersede' => sub {
my $c = shift;
# copy from the cookie
$c->param($_ => $c->session->{$_}) for qw(name username password);
$c->render(template => 'post',
id => '',
body => $c->param('body'),
group => $c->param('group'),
subject => $c->param('subject'),
supersedes => $c->param('supersedes'),
references => $c->param('references'));
} => 'supersede';
post '/post' => sub {
my $c = shift;
$c->session(expiration => time + 7 * 24 * 60 * 60); # one week
my $username = $c->param('username');
return $c->error("No username") unless $username or $ENV{NEWS_MODE} and $ENV{NEWS_MODE} eq "NOAUTH";
$c->session(username => $username);
my $password = $c->param('password');
return $c->error("No password") unless $password or $ENV{NEWS_MODE} and $ENV{NEWS_MODE}eq "NOAUTH";
$c->session(password => $password);
my $name = $c->param('name');
return $c->error("No from address specified") unless $name;
$name =~ s/[^[:graph:] ]//g;
return $c->error("From address does not have the format 'Your Name <mail\@example.org>'") unless $name =~ /<\S+@\S+\.\S+>/;
$c->session(name => $name);
my $group = $c->param('group');
return $c->error("No group") unless $group;
$group =~ s/[^[:graph:]]//g;
return $c->error("No group") unless $group;
my $references = $c->param('references');
my $supersedes = $c->param('supersedes');
my $subject = $c->param('subject');
return $c->error("No subject") unless $subject;
# $subject = encode("MIME-Header", $subject);
my $body = $c->param('body');
return $c->error("No body") unless $body;
$body = wrap($body) if $c->param('wrap');
my $nntp = Net::NNTP->new() or return $c->render(template => 'noserver');
$nntp->authinfo($username, $password) if $username and $password;
my $article = [];
push(@$article, "From: $name\r\n");
push(@$article, "Subject: $subject\r\n");
push(@$article, "Newsgroups: $group\r\n");
push(@$article, "References: $references\r\n") if $references;
push(@$article, "Supersedes: $supersedes\r\n") if $supersedes;
push(@$article, "MIME-Version: 1.0\r\n");
push(@$article, "Content-Type: text/plain; charset=UTF-8\r\n");
push(@$article, "Content-Transfer-Encoding: 8bit\r\n");
push(@$article, "\r\n");
push(@$article, map { "$_\r\n" } split(/\r?\n/, encode('UTF-8', $body)));
app->log->debug(join("", @$article));
my $ok = $nntp->post($article);
$cache->set("$group list " => undef) if $ok; # includes space and no page number
$nntp->quit;
$c->render('posted', group => $group, ok => $ok);
} => 'post';
get '/latest' => sub {
my $c = shift;
my $list = cached("news " . ($ENV{NEWS_GROUPS} || "*"), sub {
my $nntp = Net::NNTP->new() or return 'error';
my $since = time() - 7 * 24 * 60 * 60; # one week
my $ids = $nntp->newnews($since, $ENV{NEWS_GROUPS} || "*");
$ids = [@$ids[$#$ids - $per_page .. $#$ids]] if @$ids > $per_page;
my $articles = [];
my $parser = DateTime::Format::Mail->new->loose;
for my $id (@$ids) {
my $head = $nntp->head($id);
next unless $head;
my $headers = Mojo::Headers->new;
for my $line (@$head) {
$headers->parse("$line\r\n");
}
$headers->parse("\r\n"); # make sure it finishes correctly
my $subject = decode("MIME-Header", $headers->header("subject")) || "?";
my ($tag) = $subject =~ /\[(.*?)\]/;
my $from = no_email(decode("MIME-Header", $headers->header("from")));
my $date = $headers->header("date");
app->log->debug("$from/$subject/$date") unless $date;
my $dt = $parser->parse_datetime($date);
$date = [$dt->ymd, sprintf("%02d:%02d", $dt->hour, $dt->minute)];
my $newsgroups = [split(/\s*,\s*/, decode("MIME-Header", $headers->header("newsgroups")) || "")];
my $group = "@$newsgroups";
my $url = $c->url_for('article', group => $group, id => $id); # $num is not available
push(@$articles, {
id => $id,
tag => $tag,
url => $url,
group => $group,
from => $from,
subject => $subject,
date => $date,
newsgroups => $newsgroups, });
};
$nntp->quit;
return $articles });
return $c->render(template => 'noserver') if $list eq 'error';
$c->render(template => 'latest', list => $list);
} => 'latest';
app->start;
__DATA__
@@ index.html.ep
% layout "default";
% title 'News';
( run in 3.398 seconds using v1.01-cache-2.11-cpan-97f6503c9c8 )