2010/03/22

Bloggerで閲覧制限したブログのフィードを公開する方法

この記事より簡単な方法を準備してみました。[2011.04.19]
閲覧制限したBloggerブログのフィードを公開する方法(改)


Bloggerには閲覧者を制限する機能があって、プライベートなブログを作るには便利。なんだけど、ちょっとした不便がある。それは『閲覧制限をかけると記事やコメントのフィードが生成されない』というもの。ちょっと古いけど、こんな関連記事もチラホラ見かけられる。本文は特定の人以外に公開したくないけど、記事のタイトルやコメントが付いたことくらいはフィードリーダーで確認できたら便利だなぁ、という人、少しくらいはいそうだ。というより、僕が便利だ!w

というわけで、方法を用意してみた。

やり方は以下の通り。
  1. hook2feedにGoogleアカウントでログインして「create new setting」する。

    「Feed Title」にはブログ名を、「URL」にはブログURLを、がオススメ。
  2. bmail2hookにGoogleアカウントでログインして「create new setting」する。

    「hook point URL」には、hook2feedで生成された「hook point URL」を入力すること。「Trim body?」で、本文をフィードにどれだけ載せるか、を設定できる。まったく載せたくなければ、「yes」「max length = 0」とすればよい。
  3. Bloggerの設定画面の「BlogSendアドレス」に、bmail2hookで生成されたメールアドレスを追加する。
  4. hook2feedで生成された「feed URL」を、使っているフィードリーダに追加する。
これで、設定したブログに記事を追加するとフィードが更新されるようになるので、閲覧制限したブログでも更新情報をフィードリーダで読めるようになると思う。

また、コメントの追加をフィードで知りたければ、同様の手順で作ったメールアドレスをBlogger設定画面の「コメント」→「コメント通知メール」という項目に追加すればいい。

というわけで、ちょっとややこしいけど、僕自身は便利に使えてるので満足ですっ!

2010/03/19

Bloggerの通知メールをWebHookに変換するWEBサービスを作ってみた

hook2feedと同様、これもまた「とある目的」のためにGAE for Javaで作ってみた。

bmail2hook - blogger's notification e-mails to web hooks

Googleアカウントでログイン後、WebHookの送信先URLを指定して「create new setting」すると、専用のメールアドレスが作成される。作成されたメールアドレスは以下のようにして使える。
  • 作成されたメールアドレスを、Bloggerの設定画面で新規記事や新規コメントの通知メール宛先に設定する
  • bmail2hookは、受信した通知メールから、記事のタイトル・URL・本文(の一部)を抽出して、指定したURLに対してWebHookする
これもまたhook2feedと同じく、「とある目的」以外の用途が思い付かない。困った。。。無理やり例を挙げるとするならば、、、HookHubとか経由してtwitterへ転送すれば、リアルタイムなtwitterへのブログ更新通知のできあがり、なんて如何?

ちなみに、bmail2hookとhook2feedで実現したかった「とある目的」については別記事にて。

2010/03/18

WebHookを溜めてRSSフィードを生成するWEBサービスを作ってみた

とある目的(については後日改めて)で使いたくて、GAE for Javaのお勉強がてら作ってみた。WebHooksの意義を考えると本末転倒な感じもするけど、深くは考えないことにする。。。

hook2feed - create feeds from web hooks

Googleアカウントでログインして「create new setting」すると、専用の2つのURL(「hook point URL」と「feed URL」)が作成される。hook2feedは、、、
  • 「hook point URL」の方に来たWebHook(HTTP POST)の履歴(最新20件分)を溜める
  • 「feed URL」の方で、WebHook履歴からRSSフィードを生成する
というように動く。
ちなみに、PubSubHubbubに一応対応していて、WebHookされたらhubにpublishするようになってる。PubSubHubbub対応のフィードリーダ(livedoorリーダなど)で見ると、リアルタイム性を体感できるはず。

さて、「とある目的」以外でどんな用途があるか、我ながらよく分からないけど、ブラウザで閲覧中のサイトタイトル&URLとダイアログボックスで入力したメモを「hook point URL」にWebHookしてフィード化するbookmarkletを作ってみた。以下のテキストボックスに「hook point URL」の末尾10桁のIDを入力してボタンを押すと、bookmarkletとして使えるリンクができる。
「hook point URL」のID(10桁) :
気付けば、まんま「あとで読む」なんだけど、役に立つシーンはあるかしら。他にも、HookHubを使って色んなWebHookをフィード化してみると、うれしいシーンが見つかる、かもしれない。

2010/03/06

GAE for Javaでquoted-printableなメールの受信

Google App Engine for Javaでメールを受信する処理を作ってたら、quoted-printableエンコーディングされたメール本文をデコードすると、デコード結果が途中で切れてしまう現象に遭遇した。調べてみると、quoted-printableエンコーディングでいうところの「Soft Line Breaks」がうまく認識されないことが原因だったので対策してみた。

まず、「Soft Line Breaks」については、RFCのquoted-printableエンコーディング規則の中で以下のように説明されている。要は、長すぎる行には適宜「=」+「改行」を入れて分割する、というエンコーディングが行われるとのこと。
Rule #5 (Soft Line Breaks):
The Quoted-Printable encoding REQUIRES that encoded lines be no more than 76 characters long. If longer lines are to be encoded with the Quoted-Printable encoding, 'soft' line breaks must be used. An equal sign as the last character on a encoded line indicates such a non-significant ('soft') line break in the encoded text.
RFC 1521: MIME Part One
RFCにはこう書いてあるのに、試してみると、JavaMail 1.4のMimeUtility.decode()でも、Commons Codec 1.4のQuotedPrintableCodec.decode()でも、Soft Line Breaksが正しく処理されず、最初のSoft Line Breakのところまででデコード結果が切れてしまう。で、なんでかなぁと思ってQuotedPrintableCodecのドキュメントをちゃんと読んでみると、きちんと明記されていた。
Note:
Rules #3, #4, and #5 of the quoted-printable spec are not implemented yet because the complete quoted-printable spec does not lend itself well into the byte[] oriented codec framework.
QuotedPrintableCodec (Commons Codec 1.4 API)
というわけで、自分でSoft Line Breaksを取り除いてやる必要がある。MimeMessageから取り出したMimeBodyPartをデコードする処理は、例えばこんな感じ。効率の悪いコードだけど、今のところ問題なく動いているようだ。
private String _decodeBody(MimeBodyPart bp)
{
  // parse header
  String contentType = null;
  String contentEncoding = null;
  String charset = null;
  try{
    contentType = bp.getContentType();
    contentEncoding = bp.getEncoding();
  }catch(MessagingException e){
  }
  String[] elems = contentType.split(";");
  for(String elem : elems){
    if(elem.trim().startsWith("charset=")){
      charset = elem.trim().substring("charset=".length());
    }
  }
  if(charset!=null){
    if(charset.startsWith("\"")) charset = charset.substring(1);
    if(charset.endsWith("\"")) charset = charset.substring(0, charset.length()-1);
  }
  // get inputstream
  InputStream in = null;
  try{
    in = bp.getRawInputStream();
  }catch(MessagingException e){
  }
  if(in==null) return "";
  // convert quoted-printable
  if(contentEncoding!=null && contentEncoding.equals("quoted-printable")){
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int len;
    byte[] buffer = new byte[1024];
    try{
      while( (len=in.read(buffer, 0, buffer.length)) != -1 ){
        baos.write(buffer, 0, len);
      }
    }catch(IOException e){
    }
    byte[] b = baos.toByteArray();
    baos = new ByteArrayOutputStream();
    for(int j=0;j<b.length;j++){
      if(b[j]=='=' && j<b.length-1 && b[j+1]=='\n'){
        j++;
      }else{
        baos.write(b[j]);
      }
    }
    b = baos.toByteArray();
    in = new ByteArrayInputStream(b);
  }
  // decode
  if(in!=null && contentEncoding!=null){
    try{
      in = MimeUtility.decode(in, contentEncoding);
    }catch(MessagingException e){
    }
  }
  if(in==null) return "";
  // read body
  Reader r = null;
  if(charset!=null){
    try{
      r = new InputStreamReader(in, charset);
    }catch(UnsupportedEncodingException e){
    }
  }else{
    r = new InputStreamReader(in);
  }
  StringBuffer sb = new StringBuffer();
  BufferedReader br = null;
  try{
    br = new BufferedReader(r);
    String line = null;
    while( (line=br.readLine())!=null ){
      sb.append(line.trim());
      sb.append("\n");
    }
  }catch(IOException e){
  }finally{
    if(br!=null){
      try{
        br.close();
      }catch(IOException e){}
    }
  }
  return sb.toString();
}