2008/01/31


via twitter/plaggerのポスト

FrontPage - habu Wiki @ SF.jp

habuは、プラグインを組み合わせることでRSSを加工して再配信するためのソフトウェアです。簡単に言っちゃえば,Plaggerもどきです。

plaggerも好きだし、pythonも好きなら試さない訳には行きません。インストールはマイコミジャーナルが詳しいかと思います。私は、いきなりsvn/trunk取得して必要な物はeasy_installしました。

まぁ題名の件で言えば、iTunesで聞いている曲をTwitterにPostするPythonのスクリプト ? TRIVIAL TECHNOLOGIES 2.0を使えば動きそうですが、とりあえず...

habuではpluginはクラスで定義され、実行時にexecuteというメソッドが呼び出されます。気をつけなければならないのがexecuteはperl版と異なりentry単位にhookされるのではなく、全てのcontentsを持って呼び出されます。
※開発中なのかbaseとなるpluginクラスといった物はありません。
またhabuにはまだtemplateが実装されていません。ですのでポスト形式は現状プラグイン本体に記述する事になります。(いずれ改良されるかもしれません)
あと、これはいたし方ないですがsite-packageにあるxxx.pyを使うpublisher/xxx.pyでimport xxx出来ません。
今回のtwitterポストプラグインも「twitterPost.py」という名前にしてあります。
webutilsクラスやlogクラス等で、おおよそperl版と同じような事が出来ます。perlよりもpythonの方が可読性がありますし、pytnonにも負けない程のライブラリ郡もありますから、色んな事が出来るかと思います。Djangoなんかと組み合わせても面白そうです。
あと、面白いなと思ったのがコマンドライン用インタフェース「runhabu.py」に"--download-module"というオプションがあり、svnサーバからダウンロード出来る仕組みがあるようです。こちらは追々試します。
pythonが好きでplaggerも触りたい人は、こちらから初めてみてみるのも良いかもしれませんね。
で、最後に適当に作ったプラグインがコレ
habu/publisher/twitterPost.py
# -*- coding: utf-8 -*-
from twitter import Api
import habu.log as log

class TwitterPublisher(object):
  def __init__(self, config, environ):
    self.username = config.get("username", "twitter username")
    self.password = config.get("password", "twitter password")

  def execute(self, content):
    try:
      api = Api(self.username, self.password)
      for entry in content["entries"]:
        message = entry["title"] + ":"
        if len(entry["summary"]):
          message += entry["summary"]
        else:
          message += entry["description"]
        message += " " + entry["link"]
        if len(message) > 159:
          message = message[0: 159] + "..."
        api.PostUpdate(message)
    except Exception, e:
      log.error()
    else:
      log.info("twitterPost : commit")

def create(config, environ):
  return TwitterPublisher(config, environ)
あと、用意するYAMLはこんな感じ
global:
  timezone: Asia/Tokyo
  log: stdout

pipeline:
  rss_fetcher:
    - module: subscription.config
      config:
        feed:
          - http://b.hatena.ne.jp/[hatena user]/rss
    - module: filter.join
    - module: filter.sort
      config:
        reverse: True
    - module: publisher.twitterPost
      config:
        username: [twitter username]
        password: [twitter password]
deps相当、Crypt相当の物はまだ実装されていませんのでご利用は計画的に。
Posted at by




昨日書いた「pythonで動作するPlagger「habu」でtwitterにポストするプラグイン書いた」を作者の方がご覧になられ、野良プラグイン置き場のcommit権限を頂きました。
さっそく、昨日の「twitterPost.py」もcommitさせて頂きました。
で、今日はperl版のPublish::Gmailを移植。
おおよそPublish::Gmailを取り込めてますが、viaをサポート出来ていません。
ソースは適当に(いつもながら)こんな感じ...

# -*- coding: utf-8 -*-
__author__ = 'mattn.jp@gmail.com'
__version__ = '0.1'

import smtplib
import urllib
import urlparse
import re
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
from email.MIMEImage import MIMEImage
from email.Header import Header
from email.Utils import formatdate
from HTMLParser import HTMLParser
import habu.log as log

class _EnclosureParser(HTMLParser):
  def __init__(self):
    HTMLParser.__init__(self)
    self.base = ""
    self.files = []

  def set_base(self, url):
    if url.endswith("/"):
      self.base = url + "index.html"
    else:
      self.base = url

  def handle_starttag(self, tag, attrs):
    if tag.lower() == "img":
      for attr in attrs:
        if attr[0] == "src":
          self.files.append(urlparse.urljoin(self.base, attr[1]))
          break

class MailPublisher(object):
  def __init__(self, config, environ):
    self.mailto = config.get("mailto", "mail to")
    self.mailfrom = config.get("mailfrom", "mail from")
    self.mailroute = config.get("mailroute", "mail route")
    self.encoding = config.get("encoding", "utf-8")

  def execute(self, content):
    try:
      if self.mailroute.has_key("host"):
        s = smtplib.SMTP(self.mailroute["host"])
      else:
        s = smtplib.SMTP()
      for entry in content["entries"]:
        html = """<html>
<body>
""" + entry["description"] + """
</body>
</html>"""
        msg = MIMEMultipart()
        msg['Subject'] = Header(entry["title"], self.encoding)
        msg['From'] = self.mailfrom
        msg['To'] = self.mailto
        msg['Date'] = formatdate()
        msg['X-Mailer'] = "pyhabu::mailPost %s" % __version__
        related = MIMEMultipart('related')
        alt = MIMEMultipart('alternative')
        related.attach(alt)
        ep = _EnclosureParser()
        try:
          ep.set_base(entry["link"])
          ep.feed(html)
        except Exception, e:
          pass
        finally:
          ep.close()
        cnt = 1
        for filename in ep.files:
          fp = urllib.urlopen(filename)
          headers = fp.info()
          if headers.has_key("content-type"):
            mimetype = headers["content-type"].replace("image/", "")
            name = "img%d.%s" % (cnt, mimetype)
            img = MIMEImage(fp.read(), mimetype, name=name)
            img['Content-ID'] = '<%s>' % name
            related.attach(img)
            html = re.compile(r"(<img.*>)").sub(
              lambda x:x.group(1).replace(filename, name), html)
            cnt += 1
        content = MIMEText(html.encode(self.encoding, "replace"), 'html', self.encoding)
        alt.attach(content)
        msg.attach(related)

        s.sendmail(self.mailfrom, self.mailto, msg.as_string())
        log.info("mailPost : commit")
    except Exception, e:
      log.error()
    finally:
      s.close()

def create(config, environ):
  return MailPublisher(config, environ)

※おっ!enclosureもサポートしてんじゃん...
で、用意するYAMLはこんな感じ...
global:
  timezone: Asia/Tokyo
  log: stdout

pipeline:
  rss_fetcher:
    - module: subscription.config
      config:
        feed:
          - http://www.example.com/index.rss
    - module: filter.join
    - module: filter.sort
      config:
        reverse: True
    - module: publisher.mailPost
      config:
        mailto: yourname@gmail.com
        mailfrom: yourname@gmail.com
        mailroute:
          host: mail.example.com
        #encoding: iso-2022-jp

結論:pythonもperlも楽しい。
Posted at by




色んな人のvimrcを見ていて意外とmapされていないのがこの技。
元はと言うとKoRoNさんから教えて貰った技で、先日公開した私のvimrcにも入っています。
" expand path
cmap <c-x> <c-r>=expand('%:p:h')<cr>/
" expand file (not ext)
cmap <c-z> <c-r>=expand('%:p:r')<cr>
これを使って例えば # vi /path/to/file/file.txt
type some text...
:pwd
/path/to/file
な状態で、ファイルのある場所にカレントディレクトリを移したいならば :cd <C-X>  <= Ctrl押しながらX とコマンドモードで<C-X>をタイプすれば :cd /path/to/file/ と補完されます。あとはENTERで移動。
結構便利だったりします。またコマンドモードで<C-Z>とすれば編集中のファイルへの絶対パスに補完されますので、例えば編集中のHTMLファイルをFirefoxで確認したいならば :!firefox-remote <C-Z>ENTER で簡単に確認出来ます。書きはじめたjavascriptを試すにはいいですね。
※Windowsの場合はvimrcで「set shellslash」してあり、かつfirefoxまでのパスが通っている状態ならば「:!firefox <C-Z>」で行けるかもしれません。(確認してません)

応用として、例えばまだインストールしていないGreaseMonkeyスクリプトをvimで閲覧/編集中に「:!firefox <C-Z>」すれば簡単にインストール出来てしまいます。UN*XユーザでいちいちファイラからFirefoxにjsファイルをドロップ...なんてメンドクサイですよね。特にcoderepos等のCVS/SVNリポジトリでグリースモンキーを見つけ中身をvimで確認してすぐさまインストールしたい!なんて時には大活躍です。
おそらく他にも使い道は沢山あるかと思います。
皆さんもvimrcに加えてみては如何でしょうか。
Posted at by