OSDN Git Service

[add]スコアツイートbot用コード
[hengband/web.git] / tools / tweet_score.py
1 #!/usr/bin/python
2 # -*- coding: utf-8
3 #
4 # OSDNのサーバでrequests_oauthlibを使う方法のメモ
5 #
6 # 1. 環境変数PYTHONUSERBASEでインストール先をWebコンテンツ上の任意のディレクトリに指定し、pipに--userオプションをつけてインストールを実行
7 #    以下は /home/groups/h/he/hengband/htdocs/score/local 以下にインストールする例
8 #
9 #    `$ PYTHONUSERBASE=/home/groups/h/he/hengband/htdocs/score/local pip install --user requests_oauthlib`
10 #
11 # 2. パスは通っているはずなのにシステムにインストールされているrequestsとurllib3が何故か読み込みに失敗するので、上でインストールしたディレクトリにコピーする
12 #
13 #    `$ 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`
14 #
15 # 3. sys.path.appendで上でインストールしたディレクトリにパスを通してからrequests_oauthlibをimportする
16 #
17 #    `import sys`
18 #    `sys.path.append('/home/groups/h/he/hengband/htdocs/score/local/lib/python2.7/site-packages')`
19 #    `import requests_oauthlib`
20 #
21
22 import sys
23 import ConfigParser
24 import sqlite3
25
26
27 def get_config(config_file):
28     ini = ConfigParser.ConfigParser()
29     ini.read(config_file)
30
31     config = {s: {i[0]: i[1] for i in ini.items(s)}
32               for s in ini.sections()}
33
34     return config
35
36
37 def get_score_data(score_db_path, score_id):
38     if score_id is None:
39         cond = 'ORDER BY score_id DESC LIMIT 1'
40     else:
41         cond = 'WHERE score_id = :score_id'
42
43     with sqlite3.connect(score_db_path) as con:
44         con.row_factory = sqlite3.Row
45         sql = '''
46 SELECT
47   *,
48   CASE
49     WHEN realm_id IS NOT NULL THEN '(' || group_concat(realm_name) || ')'
50     ELSE ''
51   END AS realms_name,
52   CASE
53     WHEN killer = 'ripe' THEN '勝利の後引退'
54     WHEN killer = 'Seppuku' THEN '勝利の後切腹'
55     ELSE killer || 'に殺された'
56   END AS death_reason
57 FROM
58  (SELECT
59     *
60   FROM
61     scores_view
62   {cond})
63 NATURAL LEFT JOIN
64   score_realms
65 NATURAL LEFT JOIN
66   realms
67 GROUP BY
68   score_id
69 '''.format(cond=cond)
70         c = con.execute(sql, {'score_id': score_id})
71         score = c.fetchall()
72
73     return score[0] if len(score) == 1 else None
74
75
76 def create_tweet(score_data):
77     summary = (u"【新着スコア】{personality_name}{name} Score:{score} "
78                u"{race_name} {class_name}{realms_name} {death_reason} {depth}階"
79                ).format(**score_data)
80
81     dump_url = ("http://hengband.osdn.jp/score/show_dump.php?score_id={}"
82                 ).format(score_data['score_id'])
83     screen_url = ("http://hengband.osdn.jp/score/show_screen.php?score_id={}"
84                   ).format(score_data['score_id'])
85
86     tweet = (u"{summary}\n\n"
87              u"ダンプ: {dump_url}\n"
88              u"screen: {screen_url}\n"
89              u"#hengband"
90              ).format(summary=summary,
91                       dump_url=dump_url,
92                       screen_url=screen_url)
93     return tweet
94
95
96 def tweet(oauth, tweet_contents):
97     from requests_oauthlib import OAuth1Session
98     from requests.adapters import HTTPAdapter
99     from requests import codes
100     from logging import getLogger
101     logger = getLogger(__name__)
102
103     twitter = OAuth1Session(**oauth)
104
105     url = "https://api.twitter.com/1.1/statuses/update.json"
106
107     params = {"status": tweet_contents}
108     twitter.mount("https://", HTTPAdapter(max_retries=5))
109
110     logger.info("Posting to Twitter...")
111     logger.info(u"Tweet contents:\n{}".format(tweet_contents))
112     res = twitter.post(url, params=params)
113
114     if res.status_code == codes.ok:
115         logger.info("Success.")
116     else:
117         logger.warning("Failed to post: {code}, {json}"
118                        .format(code=res.status_code, json=res.json()))
119
120
121 def parse_option():
122     from optparse import OptionParser
123     parser = OptionParser()
124     parser.add_option("-s", "--score-id",
125                       type="int", dest="score_id",
126                       help="Target score id.\n"
127                            "If this option is not set, latest score is used.")
128     parser.add_option("-c", "--config",
129                       type="string", dest="config_file",
130                       default="tweet_score.cfg",
131                       help="Configuration INI file [default: %default]")
132     parser.add_option("-l", "--log-file",
133                       type="string", dest="log_file",
134                       help="Logging file name")
135     parser.add_option("-n", "--dry-run",
136                       action="store_true", dest="dry_run",
137                       default=False,
138                       help="Output to stdout instead of posting to Twitter.")
139     return parser.parse_args()
140
141
142 def setup_logger(log_file):
143     from logging import getLogger, StreamHandler, FileHandler, Formatter, INFO
144     logger = getLogger(__name__)
145     logger.setLevel(INFO)
146     sh = StreamHandler()
147     logger.addHandler(sh)
148     if log_file:
149         formatter = Formatter('[%(asctime)s] %(message)s')
150         fh = FileHandler(log_file)
151         fh.setFormatter(formatter)
152         logger.addHandler(fh)
153
154
155 if __name__ == '__main__':
156     (options, arg) = parse_option()
157     setup_logger(options.log_file)
158     from logging import getLogger
159     logger = getLogger(__name__)
160
161     try:
162         config = get_config(options.config_file)
163         if 'Python' in config:
164             sys.path.append(config['Python']['local_lib_path'])
165         score_data = get_score_data(config['ScoreDB']['path'],
166                                     options.score_id)
167         if score_data is None:
168             logger.warning('No score data found.')
169             sys.exit(1)
170
171         tweet_contents = create_tweet(score_data)
172         if (options.dry_run):
173             print(tweet_contents)
174         else:
175             tweet(config['TwitterOAuth'], tweet_contents)
176     except Exception:
177         from traceback import format_exc
178         logger.critical(format_exc())