Friday, February 22, 2008

Nintendo DS tracker notifier

The following script notifies me via a text message when either a Cobalt Blue or Crimson Nintendo DS Lite are available. It uses dstracker.com's XML feed.

Required software: Allegro CL 8.1 Express on Linux (though it could be adapted to Windows fairly easily by not trying to detach-from-terminal).

#! /usr/local/bin/mlisp -#C

;; Nintendo DS Lite tracker notifier.

(in-package :user)

(eval-when (compile eval load)
(require :osi)
(use-package :excl.osi)
(require :aserve)
(use-package :net.aserve.client)
(require :sax)
(use-package :net.xml.dom)
(require :smtp)
(use-package :net.post-office))

(defparameter *dstracker-url* "http://dstracker.com/rss.xml")
(defparameter *email* "...@txt.att.net")
(defparameter *re*
;; I only want to be notified of the Crimson and Cobalt versions:
"(cobalt|crimson)")

(defun read-feed (url)
(logit "reading feed from ~a" url)
(multiple-value-bind (content code headers)
(do-http-request url)
(declare (ignore headers))
(if* (not (eq 200 code))
then (error "Accessing url ~s gave http response code ~s" url code))
content))

(defun notify-user (to body ht)
(when (not (gethash body ht))
(setf (gethash body ht) t)
(logit "notify ~a" to)
(send-smtp "localhost" "dstracker" to body)))

(defun parse-feed-and-notify (xml ht)
(let* ((doc (parse-to-dom xml))
(root (dom-document-element doc))
(items (dom-list-elements-by-tag-name root "item")))
(dolist (item items)
(let* ((title (dom-list-elements-by-tag-name item "title"))
(text (dom-child-node-list (car title)))
(data (dom-data (car text))))
(when (match-re *re* data :case-fold t)
(notify-user *email* data ht))))))

(defun logit (format-string &rest format-arguments)
(format t "~@/locale-format-time/" (get-universal-time))
(apply #'format t format-string format-arguments)
(format t "~&"))

(defun doit ()
(let ((ht (make-hash-table :size 101 :test #'equalp)))
(loop
(parse-feed-and-notify (read-feed *dstracker-url*) ht)
(sleep #.(* 60 10)))))

(system:with-command-line-arguments ("l:" logfile)
(rest)
(declare (ignore rest))
(when (not logfile) (error "Must supply a log file via -l argument."))
(with-open-file (s logfile :direction :output :if-exists :always-append
:if-does-not-exist :create)
(if* (= (excl.osi:fork) 0)
then ;; child
(excl.osi:detach-from-terminal :output-stream s
:error-output-stream s)
(loop
(logit "starting up")
(doit))
else (exit 0 :quiet t))))

Image courtesy of imageshack.

No comments: