Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2008-01-06

last.fmと連携するなにかを作りたい: 頓挫篇

| 23:43 | last.fmと連携するなにかを作りたい: 頓挫篇 - わだばLisperになる を含むブックマーク はてなブックマーク - last.fmと連携するなにかを作りたい: 頓挫篇 - わだばLisperになる

レトロなLispも良いけど何か普段使うツールのようなものを何か作りたいなあと思い、アカウントを作ったものの、あまり利用していないlast.fm用の簡単なクライアントをCommon Lispで書けないものかと、ちょっと調べてみました。

サーバとの通信の手順は、解説ページにまとまっていました。

ということで、何の考えもなく、そのページを頭から読みつつ、ガチャガチャコードを書きながら動作を確認してゆくことに。

曲情報の投稿までの流れとしては、

  1. クライアント、ユーザの情報をサーバに投げる。
  2. サーバから認証用のキーと投稿用URLと待機時間が送られてくる
  3. 送られて来た情報とパスワードをMD5でエンコードし、曲の情報と一緒にして投げる

という風な感じです。

途中までは順調だったのですが、サーバとの認証のところでどうもMD5でエンコードしたものの結果が違うらしく引っ掛かってしまい、BAD AUTHとなり先に進めず。

説明では、

The MD5 response is md5(md5(your_password) + challenge),
where MD5 is the ascii-encoded, lowercase MD5 representation,
and + represents concatenation.
MD5 strings must be converted to their hex value before concatenation
with the challenge string and before submission to the final MD5 response.

ということなのですが、md5(md5(your_password) + challenge)なので、一回目のハンドシェイクで送られて来たMD5のキーとMD5でエンコードしたパスワードをくっつけ、さらにそれをMD5でエンコードする、という風に読めるのですが、どうも上手く行かず。MD5のエンコードが違ったりするのかなと思い、md5sumで確認してみても生成されるキーは同一で、それ自体には問題はない様子。うーん、それとも、認証じゃなくて全然違うところで引っ掛かってしまっているのか。

こうなると他の言語での実装を調べてみるしかなさそうな気配。

しかし、他の言語は殆ど読めないのだよなあ(;´Д`)


(defpackage :last.fm
  (:use #:cl #:drakma #:url-rewrite #:md5))

(in-package :last.fm)

(defun make-get-scrobbler-uri-string (clientid clientver user)
  (let ((base "http://post.audioscrobbler.com/?hs=true&p=1.1"))
    (concatenate 'string base 
		 "&c=" clientid
		 "&v=" clientver
		 "&u=" user)))

(defun handshake-one (clientid clientver user)
  (http-request 
   (make-get-scrobbler-uri-string clientid clientver user)))
   
(defun decode-handshake-one (clientid clientver user)
  (let ((response 
	 (http-request (make-get-scrobbler-uri-string clientid clientver user))))
    (destructuring-bind (uptodatep md5-challenge post-url interval) (ppcre:split "\\n" response)
      (list (string-equal "uptodate" uptodatep)
	    md5-challenge
	    post-url
	    (ppcre:register-groups-bind (wait) ("INTERVAL ([0-9]+)" interval)
	      (values (parse-integer wait :junk-allowed 'T)))
	    user))))

(defun string-to-md5-string (str)
  (apply #'concatenate 'string
	 (map 'list (lambda (x) (string-downcase (write-to-string x :base 16)))
	      (md5sum-sequence str))))

(defun make-md5-response (password md5-challange)
  (string-to-md5-string
   (concatenate 'string (string-to-md5-string password) md5-challange)))

; - UTC date/time in YYYY-MM-DD hh:mm:ss format
(defun current-time-string ()
  (multiple-value-bind (s m h d mo y) (get-decoded-time)
    (format nil "~D-~2,'0D-~2,'0D ~2,'0D:~2,'0D:~2,'0D" y mo d h m s)))

(defun make-submit-uri (data artist track album length)
  (destructuring-bind (uptodatep md5 post-url interval user) data
    (declare (ignore uptodatep))
    (values 
     (concatenate 'string 
		  post-url
		  "?u=" user
		  "&s=" (make-md5-response "password" md5)
		  "&a0=" (url-encode artist)
		  "&t0=" (url-encode track)
		  "&b0=" (url-encode album)
		  "&m0="		;mbid
		  "&l0=" length
		  "&i0=" (url-encode (current-time-string)))
     interval)))

(defun scrobble-current-song (artist track album length)
  (multiple-value-bind (uri wait)
      (make-submit-uri (decode-handshake-one "tst" "1.0" "g000001")
		       artist
		       track
		       album
		       length)
    (sleep wait)
    (http-request uri)))

;; TEST
(print (scrobble-current-song "Bonnie Pink"
			      "Private Laughter"
			      "Even So"
			      "179"))


BBN-LISPのADVICE

| 22:11 | BBN-LISPのADVICE - わだばLisperになる を含むブックマーク はてなブックマーク - BBN-LISPのADVICE - わだばLisperになる

comp.lang.lispを眺めていたら、SymbolicsのLispマシンのマニュアルがbitsaversにアップされたとのことで、早速ダウンロードして眺めたりしておりました。

それで、bitsaversの他のディレクトリを探索していたら、BBN-LISPのマニュアルがありました。

BBN-LISPはINTERLISPの前身ともいえる存在です。

それで、Symbolicsそっちのけで眺めてみていたら、19章(P423)にADVICEという文字が。

Emacsを使っている方だったらdefadviceは知っているかと思うのですが、それと同じパターンの機構で、ある関数にフックをかけてカスタマイズするという仕組のようです。

CLOSならビフォアとアフターメソッド的ともいえるかもしれません。BBN-LISP、INTERLISPには、Daniel G. Bobrowが深く関与していますが、CLOSにも関与しているようなので、そういう影響もあるのかもしれません。BBN-LISP→INTERLISP→COMMON LOOPS→CLOSという流れ。

しかしそれよりも、このマニュアルが出たのは、1971、2年ということで、こういう仕組みが35年前に考えられていて実装されていたということに驚かされました。

他にも先進的な試みが沢山あるようで、INTERLISPの特徴は殆どBBN-LISPの時点で既に実装されているようです。

こんなことに感動しているのは自分くらいかもしれませんが、とりあえず記念にメモ(´▽`*)

Practical Common Lisp (21)

| 21:21 | Practical Common Lisp (21) - わだばLisperになる を含むブックマーク はてなブックマーク - Practical Common Lisp (21) - わだばLisperになる

Practical Common Lisp 第5章5. Functionsを読んでメモしてみています。

Rest Parameters

  • 引数の扱いには、様々あるが引数の数を可変にしたいことがある。例えば、FORMATは、2つ以上の引数を取るが、2つ目以降は必要に応じて与える引数が増減する。+も同様。0個の場合もあり、(+)は0となる。
(format t "hello, world")
(format t "hello, ~a" name)
(format t "x: ~d y: ~d" x y)
(+)
(+ 1)
(+ 1 2)
(+ 1 2 3)
  • 必須引数や、オプショナル引数等をやりくりして対応することもできるが、対応させるのは困難。。ちなみに取れる最低限50以上は引数が取れることを保証することが規格で定められている。実装により上限は異なるが、CALL-ARGUMENTS-LIMITで処理系の上限を確認できる。(SBCL 1.0.13では、1,152,921,504,606,846,975だった)
  • このような問題を解決するのが、&RESTパラメータで、&RESTに指定された変数がリストとなり、以降の引数が格納される。FORMATや、+は下記のように定義できる。
(defun format (stream string &rest values) ...)
(defun + (&rest numbers) ...)