summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Main.elm98
-rw-r--r--elm-package.json16
-rwxr-xr-xgen.pl62
-rw-r--r--index.html23
5 files changed, 201 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3161d4e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+elm-stuff
+intel.js
diff --git a/Main.elm b/Main.elm
new file mode 100644
index 0000000..961c13a
--- /dev/null
+++ b/Main.elm
@@ -0,0 +1,98 @@
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Http
+import Task
+import Date
+import Time exposing (..)
+import Json.Decode as Decode
+
+main = Html.program
+ { init = (
+ { lastFetch = 0
+ , state = Fetching
+ , feed = []
+ }, fetch)
+ , view = view
+ , update = update
+ , subscriptions = \_ -> Time.every minute Tick
+ }
+
+
+type alias Article =
+ { title : String
+ , feed : String
+ , ts : Time
+ , url : String
+ }
+
+decodeArticle : Decode.Decoder Article
+decodeArticle = Decode.map4 Article
+ (Decode.field "title" Decode.string)
+ (Decode.field "feed" Decode.string)
+ (Decode.field "ts" Decode.float)
+ (Decode.field "url" Decode.string)
+
+
+type State
+ = Start
+ | Current
+ | Fetching
+ | FetchError
+
+type alias Model =
+ { lastFetch : Time
+ , state : State
+ , feed : List Article
+ }
+
+type Msg
+ = Tick Time
+ | Data (Result Http.Error (List Article))
+ | SetTime Time
+
+
+fetch : Cmd Msg
+fetch = Http.send Data (Http.get "feeds.json" (Decode.list decodeArticle))
+
+
+update : Msg -> Model -> (Model, Cmd Msg)
+update msg model = case msg of
+ Tick _ -> ({ model | state = Fetching }, fetch)
+ Data (Err _) -> ({ model | state = FetchError }, Cmd.none)
+ SetTime t -> ({ model | lastFetch = t }, Cmd.none)
+ Data (Ok r) ->
+ ( { model | state = Current, feed = r }
+ , Task.perform SetTime now
+ )
+
+
+-- Surely there must be an easier way to do this
+fmtts : Time -> String
+fmtts t = let d = Date.fromTime t in
+ toString (Date.day d)
+ ++ " "
+ ++ toString (Date.month d)
+ ++ " "
+ ++ toString (Date.year d)
+ ++ " "
+ ++ String.padLeft 2 '0' (toString (Date.hour d))
+ ++ ":"
+ ++ String.padLeft 2 '0' (toString (Date.minute d))
+
+
+view : Model -> Html Msg
+view model =
+ let
+ state = em [ class (toString model.state) ]
+ [ if model.lastFetch == 0
+ then text "Loading feeds..."
+ else text (fmtts model.lastFetch) ]
+ item n = article []
+ [ a [href n.url] [ text n.title ]
+ , i []
+ [ text (fmtts (n.ts*1000))
+ , text " - "
+ , text n.feed
+ ]
+ ]
+ in div [] (state :: List.map item model.feed)
diff --git a/elm-package.json b/elm-package.json
new file mode 100644
index 0000000..04c32fe
--- /dev/null
+++ b/elm-package.json
@@ -0,0 +1,16 @@
+{
+ "version": "1.0.0",
+ "summary": "Blicky.net feed reader",
+ "repository": "https://g.blicky.net/bintel.git",
+ "license": "MIT",
+ "source-directories": [
+ "."
+ ],
+ "exposed-modules": [],
+ "dependencies": {
+ "elm-lang/core": "5.1.1 <= v < 6.0.0",
+ "elm-lang/html": "2.0.0 <= v < 3.0.0",
+ "elm-lang/http": "1.0.0 <= v < 2.0.0"
+ },
+ "elm-version": "0.18.0 <= v < 0.19.0"
+}
diff --git a/gen.pl b/gen.pl
new file mode 100755
index 0000000..3afcc34
--- /dev/null
+++ b/gen.pl
@@ -0,0 +1,62 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my @feeds = (
+ [ 'Security.nl', 'https://www.security.nl/rss/headlines.xml' ],
+ [ 'r/netsec', 'https://www.reddit.com/r/netsec/.rss' ],
+ [ 'Threatpost', 'https://threatpost.com/feed' ],
+ [ 'BleepingComputer', 'https://www.bleepingcomputer.com/feed/' ],
+ #[ 'AusCERT', 'https://www.auscert.org.au/rss.html' ],
+ [ 'Naked Security', 'https://nakedsecurity.sophos.com/feed/' ],
+ [ 'NCSC', 'https://www.ncsc.nl/rss/beveiligingsadviezen' ],
+);
+
+my $fn = '/var/www/www/intel/feeds.json';
+
+
+use POSIX 'strftime';
+use LWP::UserAgent;
+use XML::FeedPP;
+use JSON::XS;
+
+sub unescape { local $_ = shift; s/&(amp|quot|#(\d+));/$2 ? chr $2 : +{qw{amp & quot "}}->{$1}/eg; $_ }
+
+
+my @items;
+
+for my $feed (@feeds) {
+ #print "Fetching $feed->[0]\n";
+ my $ua = LWP::UserAgent->new;
+ $ua->agent("Yorhel's RSS reader");
+ $ua->timeout(30);
+ my $res = $ua->get($feed->[1]);
+ #print "got\n";
+
+ if($res->is_success) {
+ my $X = eval { XML::FeedPP->new(scalar $res->decoded_content()); };
+ if(!$X) {
+ warn "Parsing $feed->[0]: $@\n";
+ next;
+ }
+ for my $i ($X->get_item()) {
+ warn "No date/time in feed '$feed->[0]'\n" if !$i->pubDate();
+ push @items, {
+ feed => $feed->[0],
+ title => scalar unescape($i->title()),
+ url => scalar $i->link(),
+ ts => scalar XML::FeedPP::Util::get_epoch($i->pubDate()), # XXX: Use of internal function
+ };
+ }
+ } else {
+ warn "Unable to fetch '$feed->[1]': ".$res->status_line()."\n";
+ }
+}
+
+@items = sort { $b->{ts} <=> $a->{ts} } @items;
+
+open my $F, '>', "$fn~" or die $!;
+print $F encode_json \@items;
+close $F;
+rename "$fn~", $fn or die $!;
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..f6e18a6
--- /dev/null
+++ b/index.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <title>Security Feeds</title>
+ <style type="text/css">
+ body { background: #1F1F1F; color: #fff; font-family: "Dejavu Sans", "Arial"; overflow: hidden }
+ h1 { font-size: 60px; margin: 0 0 30px 0 }
+ article { width: 90%; margin: 10px 3% }
+ a { display: block; font-size: 30px; color: #fff; text-decoration: none; font-weight: bold }
+ i { color: #999; font-style: normal; font-size: 14px }
+ em { position: absolute; top: 5px; right: 5px; font-size: 12px; color: #999; font-style: normal }
+ em.Fetching { color: #ffff00 }
+ em.FetchError { color: #f00 }
+ </style>
+ </head>
+<body>
+<h1>Security Feeds</h1>
+<main id="main">Loading Javascript...</main>
+<script src="intel.js" type="application/javascript"></script>
+<script type="application/javascript">
+Elm.Main.embed(document.getElementById('main'));
+</script>
+</body>
+</html>