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




意外と知られていないんですね。ビジュアル選択って
vimで選択範囲を置換
うわーん。これやりかたかったんだよー!知らなかったよー!
Powered by Vim 同じネタを説明しても面白くないので、今日はビジュアル選択後に行うアクションについて...
「'<'>」の後には、「s(substitute)」だけでなく「g(global)」や「v(vglobal)」を書く事もでき、行単位でのビジュアル選択(正式にはlinewise-visual選択)を行った行に対して絞込みを行い、その上で置換を行う事も出来ます。
例えば 問題
※以下の阿藤について間違っている物に×を入れよ
(  ) 俺は阿藤会だ
(  ) 俺こそ阿藤下位だ
(  ) 僕も阿藤回だ
(  ) リッチに阿藤買いだ
(  ) 実は私の従兄弟が阿藤快だ
(  ) 叔父が阿藤飼いだ
(  ) 海で阿藤貝を拾った
(  ) お前、阿藤甲斐性あるな
こんなテキストならば、「(  )」が付いている行を選択して
:'<,'>v/阿藤快/s/(  )/(×)/g
でおしまい。
※「:」を押した時点で「'<,'>」は補完されます。
意味は、ビジュアル選択している部分から「v」で「阿藤快」の含まれない行を抜き出し、その結果に対して「s」で「(  )」を「(×)」に置換するという物です。

また、例えばテキストファイルに書かれた以下の様なスケジュール一覧があったとします。
予定表
1. 09:00 出社
2. 10:00 会議(午前の部)
3. 12:00 昼休憩
4. 13:00 会議(午後の部)
     ここで仕様を煮詰める
5. 16:00 内部ミーティング
6. 16:30 資料作成
7. 17:30 客先にて打ち合わせ
昼休憩の後に項目番号4として「13:30 来客予定」を入れたくなったらどうしますか?
4から7までを一つずつ足して行きますか?
vimなら4で始まる行から7で始まる行までを選択して
:'<,'>g/^\d/exec "normal 0\<c-a>" とすれば4以降が1個ずつずれるので、5の上から4で書き始めればよいのです。
vimではノーマルモード時、数値の上で<c-a>を押すと数値がインクリメントされる(<c-x>でデクリメント)という機能があるので、これを利用して先頭行の数字に対して<c-a>キーを送信しています。

さらに会議(午後の部)の開始が1時間が遅れるとなった場合、「13:00」を含む行から「17:30」を含む行まで選択して
:'<,'>g/^\d/s/\(\d\d\):/\=printf("%02d:", submatch(1)+1)/ でおしまい。
先頭が数字で始まる行に対して「s」で「数値+数値+":"」を検索し値に1足し、printfでゼロ付き文字にして置換しています。
※printf()はvim7でしか動きません。
少し工夫すれば30分足して60分になった物は1時間繰り上げる...なんて事も出来るでしょうね。

vimってパズルみたいで面白いですよね。方法はこれだけでなく、人によっては私よりも手数の少ない方法を使われる方もいます。
凝ると色んな事が出来ますので、皆さん凄いの見つけたら教えて下さいね。
Posted at by