#!/usr/bin/python
# -*- coding: utf-8
-#
-# OSDNのサーバでrequests_oauthlibを使う方法のメモ
-#
-# 1. 環境変数PYTHONUSERBASEでインストール先をWebコンテンツ上の任意のディレクトリに指定し、pipに--userオプションをつけてインストールを実行
-# 以下は /home/groups/h/he/hengband/htdocs/score/local 以下にインストールする例
-#
-# `$ PYTHONUSERBASE=/home/groups/h/he/hengband/htdocs/score/local pip install --user requests_oauthlib`
-#
-# 2. パスは通っているはずなのにシステムにインストールされているrequestsとurllib3が何故か読み込みに失敗するので、上でインストールしたディレクトリにコピーする
-#
-# `$ cp -a /usr/lib/python2.7/dist-packages/requests /usr/lib/python2.7/dist-packages/urllib3 /home/groups/h/he/hengband/htdocs/score/local/lib/python2.7/site-packages`
-#
-# 3. sys.path.appendで上でインストールしたディレクトリにパスを通してからrequests_oauthlibをimportする
-#
-# `import sys`
-# `sys.path.append('/home/groups/h/he/hengband/htdocs/score/local/lib/python2.7/site-packages')`
-# `import requests_oauthlib`
-#
+
+'''スコアをツイートする
+
+OSDNのサーバでrequests_oauthlibを使う方法のメモ
+
+1. 環境変数PYTHONUSERBASEでインストール先をWebコンテンツ上の任意のディレクトリに指定し、
+ pipに--userオプションをつけてインストールを実行
+ 以下は /home/groups/h/he/hengband/htdocs/score/local 以下にインストールする例
+
+ `$ PYTHONUSERBASE=/home/groups/h/he/hengband/htdocs/score/local
+ pip install --user requests_oauthlib`
+
+2. パスは通っているはずなのにシステムにインストールされているrequestsとurllib3が何故か読み込みに失敗するので、
+ 上でインストールしたディレクトリにコピーする
+
+ `$ cp -a /usr/lib/python2.7/dist-packages/requests
+ /usr/lib/python2.7/dist-packages/urllib3
+ /home/groups/h/he/hengband/htdocs/score/local/lib/python2.7/site-packages`
+
+3. sys.path.appendで上でインストールしたディレクトリにパスを通してからrequests_oauthlibをimportする
+
+ `import sys`
+ `sys.path.append('/home/groups/h/he/hengband/htdocs/score/local/lib/python2.7/site-packages')`
+ `import requests_oauthlib`
+'''
import sys
-import ConfigParser
+import datetime
import sqlite3
import gzip
import re
+import config
-def get_config(config_file):
- ini = ConfigParser.ConfigParser()
- ini.read(config_file)
-
- config = {s: {i[0]: i[1] for i in ini.items(s)}
- for s in ini.sections()}
+def get_score_data(score_id):
+ '''DBからスコアデータを取得する。
- return config
+ Args:
+ score_id: 取得するスコアのスコアID。
+ Noneの場合最新のスコアを取得する。
-
-def get_score_data(score_db_path, score_id):
+ Returns:
+ 取得したスコアのデータを格納した辞書。
+ 指定のスコアIDに該当するスコアが見つからない場合None。
+ '''
if score_id is None:
cond = 'ORDER BY score_id DESC LIMIT 1'
else:
cond = 'WHERE score_id = :score_id'
- with sqlite3.connect(score_db_path) as con:
+ with sqlite3.connect(config.config['ScoreDB']['path']) as con:
con.row_factory = sqlite3.Row
sql = '''
SELECT
return score[0] if len(score) == 1 else None
-def get_death_reason_detail(score_id):
+def get_daily_score_stats(year, month, day):
+ '''DBから指定した日付のスコア統計データを得る
+
+ Args:
+ year: 指定する年。
+ month: 指定する月。
+ day: 指定する日。
+
+ Returns:
+ 取得したスコア統計データを格納した辞書。
+ 'total_count': 総スコア件数, 'winner_count': 勝利スコア件数
'''
- ダンプファイル内から詳細な死因を取得する
- @param score_id ダンプファイルのスコアID
- @return 詳細な死因を表す文字列。ダンプファイルが無い、もしくは詳細な死因が見つからなかった場合None。
+ with sqlite3.connect(config.config['ScoreDB']['path']) as con:
+ con.row_factory = sqlite3.Row
+ sql = '''
+SELECT
+ count(*) AS total_count,
+ count(winner = 1 OR NULL) AS winner_count
+FROM
+ scores
+WHERE
+ date >= date('{target_date}') AND date < date('{target_date}', '+1 day')
+'''.format(target_date=datetime.date(year, month, day).isoformat())
+ c = con.execute(sql, {})
+ score = c.fetchall()
+
+ return score[0]
+
+
+def get_death_reason_detail(score_id):
+ '''ダンプファイル内から詳細な死因を取得する。
+
+ Args:
+ score_id: ダンプファイルのスコアID。
+
+ Returns:
+ 詳細な死因を表す文字列。
+ ダンプファイルが無い、もしくは詳細な死因が見つからなかった場合None。
'''
subdir = (score_id // 1000) * 1000
try:
return match.group(1) if match else None
-def create_tweet(score_db, score_id):
- score_data = get_score_data(score_db, options.score_id)
+def create_tweet(score_id):
+ '''ツイートするメッセージを生成する。
+
+ Args:
+ score_id: 指定するスコアID。Noneの場合最新のスコアを取得する。
+
+ Returns:
+ 生成したツイートメッセージ文字列。
+ なんらかの理由により生成できなかった場合None。
+ '''
+ score_data = get_score_data(score_id)
if score_data is None:
return None
- death_reason_detail = get_death_reason_detail(score_id)
+ death_reason_detail = get_death_reason_detail(score_data['score_id'])
if death_reason_detail is None:
death_reason_detail = (u"{0} {1}階"
.format(score_data['death_reason'],
score_data['depth']))
- summary = (u"【新着スコア】{personality_name}{name} Score:{score} "
- u"{race_name} {class_name}{realms_name} {death_reason_detail}"
+ summary = (u"【新着スコア】{personality_name}{name} Score:{score}\n"
+ u"{race_name} {class_name}{realms_name}\n"
+ u"{death_reason_detail}"
).format(death_reason_detail=death_reason_detail, **score_data)
dump_url = ("https://hengband.osdn.jp/score/show_dump.php?score_id={}"
return tweet
-def tweet(oauth, tweet_contents):
+def create_daily_stats_tweet(year, month, day):
+ '''デイリースコア統計データのツイートを生成する
+
+ Args:
+ year: 指定する年。
+ month: 指定する月。
+ day: 指定する日。
+
+ Returns:
+ 生成したツイートメッセージ文字列。
+ なんらかの理由により生成できなかった場合None。
+ '''
+ daily_stats = get_daily_score_stats(year, month, day)
+
+ tweet = (u"{year}年{month}月{day}日のスコア\n"
+ u"全 {total_count} 件, 勝利 {winner_count} 件\n"
+ u"#hengband"
+ ).format(year=year, month=month, day=day,
+ **daily_stats)
+
+ return tweet
+
+
+def tweet(tweet_contents):
+ '''ツイートする。
+
+ Args:
+ oauth: requests_oauthlib.OAuth1Sessionの引数に渡すOAuth認証パラメータ。
+ tweet_contents: ツイートする内容を表す文字列。
+ '''
from requests_oauthlib import OAuth1Session
from requests.adapters import HTTPAdapter
from requests import codes
from logging import getLogger
logger = getLogger(__name__)
- twitter = OAuth1Session(**oauth)
+ twitter = OAuth1Session(**config.config['TwitterOAuth'])
url = "https://api.twitter.com/1.1/statuses/update.json"
def parse_option():
+ '''コマンドライン引数をパースする。
+
+ Returns:
+ パースした結果を表す辞書。OptionParser.parse_args()のドキュメント参照。
+ '''
from optparse import OptionParser
parser = OptionParser()
- parser.add_option("-s", "--score-id",
- type="int", dest="score_id",
- help="Target score id.\n"
- "If this option is not set, latest score is used.")
+ parser.add_option(
+ "-s", "--score-id",
+ type="int", dest="score_id",
+ help="Tweet score with specified id.\n"
+ "When this option and -d are not set, latest score is specified.")
+ parser.add_option(
+ "-d", "--daily-stats",
+ type="string", dest="stats_date",
+ help="Tweet statistics of the score of the specified day.")
parser.add_option("-c", "--config",
type="string", dest="config_file",
default="tweet_score.cfg",
def setup_logger(log_file):
+ '''ロガーをセットアップする。
+
+ Args:
+ log_file: ロガーの出力先ファイル名。
+ Noneの場合、ファイルには出力せず標準エラー出力のみに出力する。
+ '''
from logging import getLogger, StreamHandler, FileHandler, Formatter, INFO
logger = getLogger(__name__)
logger.setLevel(INFO)
logger = getLogger(__name__)
try:
- config = get_config(options.config_file)
- if 'Python' in config:
- sys.path.append(config['Python']['local_lib_path'])
+ config.parse(options.config_file)
+ if 'Python' in config.config:
+ sys.path.append(config.config['Python']['local_lib_path'])
+
+ if options.stats_date:
+ target_datetime = datetime.datetime.strptime(
+ options.stats_date, "%Y-%m-%d")
+ tweet_contents = create_daily_stats_tweet(
+ target_datetime.year,
+ target_datetime.month,
+ target_datetime.day
+ )
+ else:
+ tweet_contents = create_tweet(options.score_id)
- tweet_contents = create_tweet(config['ScoreDB']['path'],
- options.score_id)
if tweet_contents is None:
logger.warning('No score data found.')
sys.exit(1)
if (options.dry_run):
- print(tweet_contents)
+ print(tweet_contents.encode("UTF-8"))
else:
- tweet(config['TwitterOAuth'], tweet_contents)
+ tweet(tweet_contents)
except Exception:
from traceback import format_exc
logger.critical(format_exc())