xyttrの色設定 (xyttr Advent Calendar 7日目)

この記事はxyttr Advent Calendar 2011の記事です。

READMEリファレンスには書いてないのですが、ユーザー名等の色を変更するための変数が4つ用意されています。

  • xyttr:*username-style* -- ユーザー名
  • xyttr:*hashtag-style* -- ハッシュタグ
  • xyttr:*favorite-star-style* -- お気に入りの★
  • xyttr:*separater-style* -- セパレータ

URLはclickable-uriという便利な拡張があるのでxyttrではあえて色付けをしていません。

設定例

(in-package :xyttr)

(setq *username-style* '(:keyword 1)
      *hashtag-style* '(:keyword 0 :bold)
      *favorite-star-style* '(:foreground 11)
      *separater-style* '(:foreground 14)
      )

http://gyazo.com/9b94fcb73b24b3b767e95bd487cab5a6.png

しかしこれ、上2つはregexp-keyword-list、下2つはset-text-attributeで色を付けてるため、パラメータの指定の仕方が異なると残念な事になっています。

前者はset-text-attributeのキーワード引数部分のリストで、後者はregexp-keyword-listの引数の'color'の部分のリストで指定します。
統一フォーマットからそれぞれ変換して設定するようにしても良いのですが、set-text-attributeではキーワード色を扱えないのが悩み所…

この辺は将来仕様が変わると思いますのでご注意ください。

Growl連携 その1 (xyttr Advent Calendar 6日目)

この記事はxyttr Advent Calendar 2011の記事です。

xyzzyにはEmacsのような画像のインライン表示機能がないのでxyttrではアイコン画像を描画できないのですが、アイコンちょっと確認したいなーけどxyzzyから離れたくないなーという時に便利なのがGrowl for Windowsです。
拙作のxl-growlを使うとxyzzyから簡単にGrowlへ通知を送る事ができるます。

(require "growl")
(in-package :xyttr)

;;; カーソル下のツイートをGrowlで表示
(defun growl-tweet ()
  (interactive)
  (w/entry (user.screen_name user.profile_image_url text)
    (growl:notify (concat "@" user.screen_name) text
                  :name "xyttr"
                  :icon user.profile_image_url)))

(define-key *xyttr-timeline-keymap* #\g 'growl-tweet)

こんな感じで通知が表示されます。
http://gyazo.com/d9846fd93c2920c3365bd59b93c759df.png

growl:notifyは単純な通知を送信しますが、growl:notify-with-url-callbackを使うとクリック時に指定URLをWEBブラウザで開くような通知を送信する事ができます。

(defun growl-tweet ()
  (interactive)
  (w/entry (id user.screen_name user.profile_image_url text)
    (let ((url (format nil "http://twitter.com/#!/~A/status/~A"
                       user.screen_name id)))
      (growl:notify-with-url-callback
       (concat "@" user.screen_name) text url
       :name "xyttr"
       :icon user.profile_image_url
       :sticky t))))

あまり意味のない例ですが、このコマンドで表示した通知をクリックするとWEBブラウザにツイートのpermalink URLが渡されて表示されます。

次はもうちょっと役に立つ例。
xyttrにはリプライ先ツイートをポップアップ表示する機能がありますが(リプライツイート上でpキー)、最近のTwitterクライアントによくある会話表示機能のようにリプライの応酬を遡って表示する機能はありません。
主に会話表示用のフォーマットというかバッファのモードを考えるのが面倒臭いからなのですが、Growl丸投げで良ければ割と簡単に実現できます。

;;; Growlで会話を表示
(defun growl-conversation ()
  (interactive)
  (labels
      ((rec (id d) ; id := status_id, d := 遡る件数
         ; ツイート1件取得
         (api-show-status-async
          :id id
          :onsuccess
          (lambda (st)
            (w/json (user.screen_name  user.profile_image_url
                     text #0=in_reply_to_status_id) st
              ; growlに送信
              (growl:notify (concat "@" user.screen_name) text
                            :name "xyttr" :sticky t
                            :icon user.profile_image_url)
              ; d>0かつ返信先ツイートがあればもう1回
              (when (and (> d 0) in_reply_to_status_id)
                (rec in_reply_to_status_id (1- d))))))))
    (case (timeline-mode buffer-timeline)
      ((:xyttr-search :xyttr-search-global)
       ; search-apiだと返信先ツイートのidが送られて来ないので詳細を取得しなおす
       (w/entry (id)
         (api-show-status-async
          :id id
          :onsuccess
          (lambda (st)
            (w/json #1=(#0# retweeted_status.in_reply_to_status_id) st
              ; 返信先ツイートのid(in_reply_to_status_id)があれば
              ; 最大5件まで遡って表示
              #2=(whenlet irtid (or . #1#)
                (rec irtid 5)))))))
      (t (w/entry #1# #2#)))))

(define-key *xyttr-timeline-keymap* #\p 'growl-conversation)

http://gyazo.com/b005b1d8a80d63b86b58951e67e691fd.png
実際に表示するとこんな感じ。



以上、xl-growlを使ったGrowlとの連携例その1でした。その2は来週くらいに。

URLを渡すブラウザの変更 (xyttr Advent Calendar 5日目)

この記事はxyttr Advent Calendar 2011の記事です。

xyttrのタイムラインバッファ上のURLにカーソルを合わせてReturnキーを押すと、システム標準のWEBブラウザにURLを渡して起動しますが、xyttr:*open-url-by* に関数を設定しておくとその関数にURLを渡すようになります。

(in-package :xyttr)

;;; google chromeでプロファイルを指定して起動するようにしてみる
(defun chrome (url &optional prf)
  (let* ((dir (merge-pathnames "AppData/Local/Google/Chrome" (si:getenv "USERPROFILE")))
                    ; XPの場合 "Local Settings/Application Data/Google/Chrome"
         (bin (concat dir "/Application/chrome.exe"))
         (udd (if prf (concat dir "/User Data/" prf)))
         (args (format nil "~@[--user-data-dir=\"~A\"~] \"~A\"" udd url)))
    (shell-execute bin nil args)))

;; "noext"というプロファイルで起動
(setq *open-url-by* #'(lambda (url) (chrome url "noext")))

www-modeを使ってxyzzy内で表示する場合、xyttrフレーム内で画面分割して表示すると便利です。

(defun open-url-other-window (url)
  (let ((www::*www-pframe-name* "xyttr"))
    (when (= (window-count) 1)
      (split-window-verticaly))
    (other-window)
    (www::www-open-remote url)))

(setq *open-url-by* #'open-url-other-window)

こんな感じに。
http://gyazo.com/0b59510fe4548cb2b847aa567129a7d3.png



以上、ブラウザの変更例でした。

Gistの表示 (xyttr Advent Calendar 4日目)

この記事はxyttr Advent Calendar 2011の記事です。

今回はタイムラインに流れてきたGistの内容をxyzzy内で表示する拡張を書いてみます。

githubGists APIを提供していて、JSON形式で簡単にGistの投稿内容を取得できます。
これを使ってコマンドを作りましょう。

(in-package :xyttr)

;;; カーソル下のGist URLの内容を表示する
(defun show-gist ()
  (interactive)
  ; カーソル下のURLを取得
  (whenlet url (expand-focused-url)
    ; gistのURLだったらIDを取得
    (whenlet id (and (string-match "https://gist\\.github\\.com/\\([0-9]+\\)\\(#.+\\)?$" url)
                     (match-string 1))
      ; Gists APIを使って投稿内容を取得する
      (let ((res (xhr:xhr-get (concat "https://api.github.com/gists/" id)
                              :key #'xhr:xhr-response-text)))
        ; 投稿者名/投稿日時/permalink/投稿内容を抽出
        (w/json (user.login updated_at html_url files) (json:json-decode res)
          ; テンポラリバッファに出力
          (with-output-to-temp-buffer ((format nil "*gist:~A*" id) t t)
            (format t "# ~A~%# ~A [~A]~%" html_url
                    (or user.login "anonymous") updated_at)
            (dolist (f files)
              (w/json (filename content size) (cdr f)
                (format t "# ~A [~A bytes]~%~40@{-~}~%~A~%~%"
                        filename size content)))))))))

(define-key *xyttr-timeline-keymap* '#\g 'show-gist)

実際に表示してみるとこんな感じに。
http://gyazo.com/5fe62085cc964a2e9941cd8ec0e18780.png

上記のコードは投稿内容が複数ファイルでも1バッファに詰めこんで表示します。
ファイルごとにバッファを分けて表示したいなーという場合は次のようにします。

(in-package :xyttr)

;; Gistのファイル1個表示
(defun popup-gist-file (id filename content)
  (let ((buf (get-buffer-create (concat "gist/" id "/" filename))))
    (erase-buffer buf)
    (with-output-to-buffer (buf)
      (princ content))
    (set-buffer buf)
    ;; せっかくなのでメジャーモード設定
    (whenlet mode (assoc filename *auto-mode-alist*
                         :test #'(lambda (fn pat) (string-match pat fn)))
      (funcall (cdr mode)))
    (set-buffer-modified-p nil buf)))

;; コマンド本体
(defun show-gist2 ()
  (interactive)
  (whenlet url (expand-focused-url)
    (whenlet id (and (string-match "https://gist\\.github\\.com/\\([0-9]+\\)\\(#.+\\)?$" url)
                     (match-string 1))
      (let* ((res (xhr:xhr-get (concat "https://api.github.com/gists/" id)
                               :key #'xhr:xhr-response-text))
             (files (json-value (json:json-decode res) files)))
        (dolist (f files)
          (w/json (filename content) (cdr f)
            (popup-gist-file id filename content)))))))

(define-key *xyttr-timeline-keymap* '#\G 'show-gist2)

保存したいなーと思ったらC-x C-n→パス指定→C-x C-s あるいは M-x save-as-dialogから保存するのが簡単です。



以上、Gist表示コマンドの実装例でした。

@ユーザー名,#ハッシュタグ,URLの取得 (xyttr Advent Calendar 3日目)

この記事はxyttr Advent Calendar 2011の記事です。

今回は、タイムラインバッファー上のリンクオブジェクト(っぽく見えるよう色付けしてるけど実際はただのテキスト)のデータを取得する関数を紹介します。

◆ ユーザー名の取得

カーソル下のユーザー名を取得するにはxyttr::focused-user関数を使ます。

(in-package :xyttr)

(defun test ()
  (interactive)
  (msgbox "~S" (focused-user)))

(define-key *xyttr-timerline-keymap* #\t 'test)

ユーザー名にカーソルを合わせてtを押した時の結果
http://gyazo.com/44c7cc8c570255da5187e12b871c8edc.png

紹介しておいてナンですが、使い途はあまり多くない気がします。

ハッシュタグの取得

カーソル下のハッシュタグ名を取得するにはxyttr::focused-hashtag関数を使ます。

(defun test ()
  (interactive)
  (msgbox "~S" (focused-hashtag)))

http://gyazo.com/6dac89d65117d3ce837577762ad07d1e.png

まだ本体の方でsaved search API(検索メモ)の対応をしてないので、お気に入りタグ機能など自作すると良いかもしれません。

◆ URLの取得

URLの取得はxyttr::focused-urlxyttr::expand-focused-urlを使います。
focused-urlは短縮URLの展開を行いません。特に理由がなければexpand-focused-urlを使うと良いでしょう。

;;; www-modeでURLを開く
(require "www/www")
(defun test ()
  (interactive)
  (whenlet url (expand-focused-url)
    (when (y-or-n-p "open ~A :" url)
      (www::www-open-remote url))))



以上、リンクっぽい箇所のデータ取得方法でした。

ツイートデータへのアクセス (xyttr Advent Calendar 2日目)

この記事はxyttr Advent Calendar 2011の記事です。

インストール方法や基本操作から始めた方が良いかなーと思いつつ、まあその辺はreadmeで事足りるでしょうという事で拡張用コードの書き方に絞って進めて行きます。
※ xyttr拡張用コードは.xyzzyに書いても良いですが、~/.xyzzy/config.l に書く事をお勧めします。

◆ ツイートデータを覗いてみる

タイムラインバッファ上のツイートについて情報を取り出すには、xyttr::entry-point関数xyttr::w/entryマクロを使います。使い方を見て行きましょう。



xyttr::entry-point関数はカーソル下にあるツイートの開始位置(直前のセパレータの先頭位置)とツイートデータ(連想リスト)を多値で返します。

;;; -*- mode:lisp; package:xyttr -*-

(in-package :xyttr)

(defun test ()
  (interactive)
  (multiple-value-bind (start data) (entry-point)
    (msgbox "~S" data)))

(define-key *xyttr-timeline-keymap* #\t 'test)

;; userパッケージやeditorパッケージからはxyttr::entry-pointのように
;; パッケージ修飾子をつけないと使用できないので、config.lでは冒頭の方で
;; (in-package :xyttr)としておくと面倒がなくて良いです。

上記のコードを~/.xyttr/config.lとして保存してからxyttrを起動します。xyttrが起動済みなら単にM-x eval-bufferすればOKです。
そして適当なツイートにカーソルを乗せてからtキーを押すとツイートのデータがメッセージボックスで表示されると思います。

http://gyazo.com/4869daf9f452d45833559e5f03959b31.png

このリストはTwitter APIより取得したJSON文字列をjson:json-decode関数でパースして得られたリストで、連想リストの形になっています。
ではここから何かデータを抜き出してみましょう。JSONリストより指定したフィールド値を取り出すxyttr::json-valueというマクロが用意されているので、まずはこれを使ってみます。

;; 以下 in-package 等は省略

;; アカウント名・投稿文・投稿日時を取得して整形し、
;; クリップボードにコピーする
(defun test ()
  (interactive)
  (multiple-value-bind (start data) (entry-point)
    (let* ((name (json-value data user.screen_name))
           (text (json-value data text))
           (date (json-value data created_at))
           (tweet (format nil "@~A: ~A [~A]" name text date)))
      (copy-to-clipboard tweet)
      (message "copied: ~A" tweet))))

json-valueマクロはJSONリスト(連想リスト)とフィールド名(シンボル)を引数に取ります。
フィールド名はJavaScriptのようにドットで連結して深い階層のフィールド名を指定する事ができます。

取り出すフィールド値が1つ2つだったらこれで良いですが、多くなるとjson-valueを使うのも面倒になるので、さらに便利なxyttr::w/jsonというマクロが用意されています。(w/〜 は所謂with-系マクロのArc流命名法です)

;; w/jsonを使って同じ処理を書いてみる。

(defun test ()
  (interactive)
  (multiple-value-bind (start data) (entry-point)
    (w/json (user.screen_name text created_at) data
      (let ((tweet (format nil "@~A: ~A [~A]" user.screen_name text created_at)))
        (copy-to-clipboard tweet)
        (message "copied: ~A" tweet))))

w/jsonは第1引数で指定されたフィールドについて第2引数のJSONリストより値を取り出し、その値をそのまま第1引数中のシンボルに束縛してbody部を評価します。
そして、startが不要な場合はもっと楽に書けてもいいよね、ということでentry-point関数とw/jsonを組み合わせたxyttr::w/entryマクロがあります。

(defun test ()
  (interactive)
  (w/entry (user.screen_name text created_at)
    (let ((tweet (format nil "@~A: ~A [~A]" user.screen_name text created_at)))
        (copy-to-clipboard tweet)
        (message "copied: ~A" tweet))))

大分すっきりしました。
もう1つ、w/entryを使って簡単なコマンドを書いてみましょう

◆ クライアントアプリのWEBサイトを表示してみる

タイムラインを見てるとたまに見慣れないアプリの名前が流れてきて気になる事があります。WEB版Twitterなら詳細表示してからアプリ名をクリックするとページに飛べますが、xyttrにはそのような機能がないのでコマンドを作ってみましょう。

クライアントアプリの情報は、

<a href="url"> アプリ名 </a>

というHTMLの形式でsourceフィールドに入っています。このURLを取り出し、WEBブラウザで表示します。

(defun open-client-url ()
  (interactive)
  (w/entry (source)
    (whenlet url (and (string-match "<a href=\"\\([^\\\"]+\\)\"" source)
                      (match-string 1))
      (shell-execute url t))))

(define-key *xyttr-timeline-keymap* '(#\c #\l) 'open-client-url)

xyttr::whenletマクロはArcからパクったユーティリティーマクロです。(参考)

M-x eval-bufferしたら、気になったツイート上でc lと入力してみましょう。
http://gyazo.com/393238295fb43daf55b7cb0a81caaa62.png
このように確認できます。



以上、ツイートデータへのアクセス方法の紹介でした。

xyttr v1.1.1 更新内容

v1.0.x あるいはそれ以前のバージョンから使用していた人向けの変更情報

タイムラインリロード処理の非同期化

APIリクエスト→バッファへの反映を非同期で実行するように変更しました。
今まではリロード時に操作がブロックされていたため、タイムラインバッファを沢山開いていると編集作業に支障をきたしたり、Twitterが死んだ時にxyttrも道連れになるという不具合がありましたが、これらが解消されました。

お気に入り★のトグル処理の非同期化

★のon/off時のAPIリクエスト→バッファへの反映を非同期で実行するように変更しました。

expand-focused-url コマンド

短縮URL上にカーソルを置いてeを押すと展開後のURLに置換します。
多重短縮URLは200が返ってくるまで展開しますが、前置引数を渡すと展開する回数を制限できます。
例: M-1 e → 1回だけ展開

リファレンス追加

インストール先は ~/site-lisp/xyttr/reference.md です。
後半に拡張コードを書きたい人向けの情報が書いてあります。

~/.xyttr/config.l のサンプルを色々修正

NetInstaller用パッケージにも含めるようにしました。
~/site-lisp/xyttr/dot_xyttr_example以下にインストールされます。

config.lロードタイミングを微妙に変更

今までは xyttr:*default-user* を設定するコードは.xyzzyに書かないとダメでしたが、config.lに含められるようになりました。