携帯に天気情報を送る

たつをさんのtenki-mail.plが使えなくなってたので、勉強もかねてpythonで作ってみた。いろいろ足りてないけど公開してみるテスト。Livedoor天気情報の地域(横浜市南区の天気)とかのURLのみ対応です。
tenki-mail.plと同じように、-mで送信先メールアドレス、-uでLivedoor天気情報のURLを指定します。

でも、おれのNokia端末だと固定長じゃないみたいでずれてしまって悲しいという罠。まあいいさ。

追記: spacerがSpacerになってた。修正して動作確認。
追記2: 頂いたTrackbackに書かれているけれども、Livedoor天気情報は地域の時間単位で気温とかが分かるようなAPIを出してないのです。だからこんなことをしなければならなかったわけで。
追記3: id:pokarimからのコメントに従って修正

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 本文例(16文字までの場合):
# 0 3 6 912151821
#曇曇曇曇曇曇曇曇 <- 今日の天気 (23:00に送ってもその日の天気)
#1820182022212019 <- 気温
# 0 0 0 0 0 0 0 0 <- 降水量
# 003060912151821
#曇曇曇曇曇曇曇曇 <- 明日の天気
#1820182022212019
# 0 0 0 0 0 0 0 0

import urllib, urlparse, re, sys, os
from optparse import OptionParser
# Mail related lib
import smtplib
from email.MIMEText import MIMEText
from email.Header import Header
from email.Utils import formatdate

#---- 設定部分
SMTPHost = 'mail.example.jp' # メールサーバ
Subject  = u'天気'          # Subject。uは残して''の中を変えてください
FromAddr = 'faw@example.jp' # Fromのアドレス

WideDisplay = True # 一行16文字より多く表示可能な携帯の場合Trueに
#WideDisplay = False # 一行16文字以内表示する携帯の場合Falseに
#---- 設定部分終わり


class Tenki():
  def __init__(self, date):
    self.date = date
    self.tenki = {}
    self.rain  = {}
    self.temp  = {}

  def setTenki(self, hour, string):
    string = string.replace(u'晴れ', u'晴') # 文字数削減
    string = string.replace(u'曇り', u'曇')
    self.tenki[hour] = string

  def setRain(self, hour, string):
    self.rain[hour] = int(string, 10)

  def setTemp(self, hour, string):
    self.temp[hour] = int(string, 10)

  def getTenki(self):
    if WideDisplay == True:
      text = " 0  3  6  9 12 15 18 21\n" # header
    else:
      text = " 0 3 6 912151821\n" # header with no space
    text = text + self.printData(self.tenki)
    text = text + self.printData(self.temp)
    text = text + self.printData(self.rain)
    return text.rstrip()

  def printData(self, data):
    string = ""
    if WideDisplay == True:
      spacer = " "
    else:
      spacer = ""
    for k, v in data.iteritems():
      if isinstance(v, int):
        string = "%s %2d" % (string, v)
      else:
        string = string + spacer + v

    if isinstance(v, int):
      return " " + string.strip() + "\n"
    else:
      return string.lstrip() + "\n"

class TenkiParser():
  def __init__(self, options):
    self.options = options

  def get(self):
    html = self.getHTML()
    data = self.parseHTML(html)
    text = self.prepareData(data)

    return text

  def getHTML(self):
    if (self.options.verbose):
      print "HTML get:" + self.options.url

    # Proxy setting
    if (self.options.proxies == None):
      url_file = urllib.urlopen(self.options.url)
    else:
      if (self.options.verbose):
        print "Proxy:" + self.options.proxy
      url_file = urllib.urlopen(self.options.url, self.options.proxies)

    html = url_file.read()
    html = unicode(html, "euc-jp") # Livedoorのページはeuc-jp

    url_file.close()

    return html

  def parseHTML(self, html):
    r = re.findall(r'</strong> - (.+?)</small>', html)
    today    = Tenki(r[0]) # 今日の天気情報作成
    tomorrow = Tenki(r[1]) # 明日の天気情報作成

    # 今日と明日の天気情報をセット
    self.setTenkiData(u'<div style="margin-top:5px;">(.+?)</div>', html,
                      today.setTenki, tomorrow.setTenki)
    self.setTenkiData(u'<small>(\d+?)mm</small>', html,
                      today.setRain, tomorrow.setRain)
    self.setTenkiData(u'<small>(\d+?)?</small>', html,
                      today.setTemp, tomorrow.setTemp)

    self.today = today
    self.tomorrow = tomorrow

  def setTenkiData(self, regexLine, html, today, tomorrow):
    r = re.findall(regexLine, html)
    for i in range(0, 8):  # 今日の分を0-21時まで3時間刻み
      today(i*3, r[i])
    for i in range(8, 16): # 明日の分を0-21時まで3時間刻み
      tomorrow((i-8)*3, r[i])

  def prepareData(self, data):
    text = self.today.getTenki() + "\n"
    text = text + self.tomorrow.getTenki()
    return text

class TenkiKeitai():
  def __init__(self, options):
    self.options = options

    t_parser = TenkiParser(options)
    self.text = t_parser.get()

    if (options.nomail == True):
      self.printMail()
      exit
    else:
      self.sendMail()

  def printMail(self):
    if (self.options.verbose):
      print u"""動作確認用表示"""

    print self.text.encode("euc-jp")

  def sendMail(self):
    if (self.options.verbose):
      print u"""メール送信:""" + self.options.mailto

    t = self.text.encode('iso-2022-jp')
    msg = self.createMessage(t)
    self.send(msg)

  def createMessage(self, body):
    encoding = "ISO-2022-JP"
    msg = MIMEText(body, 'plain', encoding)
    msg['Subject'] = Header(Subject, encoding)
    msg['From'] = FromAddr
    msg['To'] = self.options.mailto
    msg['Date'] = formatdate()
    return msg

  def send(self, msg):
    SMTPPort = 25 # 決め打ちだけどいいでしょう
    s = smtplib.SMTP(SMTPHost, SMTPPort)
    s.sendmail(FromAddr, self.options.mailto, msg.as_string())
    s.close()


def main():
  usage = "usage: %prog [options]"
  parser = OptionParser(usage=usage)
  parser.add_option("-m", "--mailto", dest="mailto",
                  help=u"""送信先メールアドレス""", metavar="MAIL")
  parser.add_option("-u", "--url", dest="url",
                    help=u"""Live Door 天気情報のURL""")
  parser.add_option("-p", "--proxy", dest="proxy",
                    help=u"""Proxy設定 ex)http://proxy.example.jp:8080""")
  parser.add_option("-n", action="store_true", dest="nomail",
                    help=u"""実際にはメールを送信しない(動作確認用)""",
                    default=False)
  parser.add_option("-v", action="store_true", dest="verbose",
                    help=u"""ログ表示を冗長にする""")
  parser.add_option("-q", action="store_false", dest="verbose",
                    help=u"""ログ表示を省略する""")

  (options, args) = parser.parse_args()

  if ((options.mailto == None) and (options.nomail == False)):
    print u"""ERROR: -mか-nを設定してください"""
    parser.print_help()
    return
  if (options.url == None):
    print u"""ERROR: urlを設定してください"""
    parser.print_help()
    return

  if (options.proxy != None):
    options.proxies = {"http": options.proxy}
  else:
    options.proxies = None

  tenkikeitai = TenkiKeitai(options)


if __name__ == '__main__':
  main()