Tweepy を使って月齢🌕を Twitter ユーザーネームに反映させる

# 🌖月齢を身近に眺めたい
Twitter のユーザーネームに月齢を表示させといたら、きれいな星を見に行くタイミングや釣りに行くタイミングを知るのに便利じゃないかと思ったのでやってみました。[当然、新月の方が星がきれいに見えるんですよ](https://www.ep.sci.hokudai.ac.jp/~mkuriki/article/cise/nupuka_anim_800.gif)。

名前は Moon Age Updater for Twitter で "MAUT"（モート）です。特に深い意味はないです。Twitter API を扱える Python ライブラリの Tweepy を利用しています。GitHub にて MIT ライセンスの下、ソースコードを公開しています。GitHub : [mkuriki1990/maut](https://github.com/mkuriki1990/maut)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331465/c1c60b46-8c1d-754e-bb79-fed5ee5cf354.png)
使用例 [私のTwitter : @mkuriki_](https://twitter.com/mkuriki_)

# 🌗使用方法
`update_profile.py` と同じディレクトリに `config.py` を作成し、中に [Twitter Developer Platform](https://developer.twitter.com/) で取得した各 Key 情報を以下のように入れておきます。

```python
CONSUMER_KEY = "xxxxxxxxxxxxxxxx"
CONSUMER_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxx"
ACCESS_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxx"
ACCESS_TOKEN_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxx"
```

Twitter API Key の取得は以下のページを参考にでもしてください。私が API key を取得した 1 年前（2019 年）とも微妙にやり方が変わってるのでよくわかりません。

* [2020年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説 - ITTI STAFF BLOG](https://www.itti.jp/web-direction/how-to-apply-for-twitter-api/)

あとは crontab 等使って定期的に動かせばいいでしょう。私は Raspberry Pi で
無駄に毎分
動かしています。
<img width="400" alt="raspberrypi" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/331465/3850a761-032f-dc61-068b-cdec8fe552a4.png">

## 🌘実行環境
* Raspbian GNU/Linux 10 (buster) 
* Python 3.7.3
* Tweepy 3.9.0

## 🌑動作内容
* 昼 12 時を境に午前中は前夜、午後は当日の夜に対応する月齢を計算
 * 月齢に応じ🌑🌒🌓🌔🌕🌖🌗🌘から適当な絵文字を選択
* アカウントのユーザーネーム（と説明欄）に月絵文字を設定
* ついでに現在時刻（実行環境のシステム時刻）もユーザーネームに反映

# 🌒プログラム内容解説
## 🌛ユーザーネームの変更方法
一番重要なユーザーネームを含むプロフィール情報を変更するには `update_profile` 関数を使えばできます。

```python
API.update_profile([name][, url][, location][, description])
```

あとはここにどういう引数を与えるか、ということだけです。各セクションごとに内容を書いておきます。

## 🌓Twitter API を設定
次の記事を参考にしました。

* [TweepyでTwitterに投稿する - Qiita](https://qiita.com/pontyo4/items/da8ba576c6281d67c0c7)

Tweepy の API オブジェクトを取得しておきます。

```python
import tweepy

# 同じディレクトリにある config.py から鍵情報を読み込む
CK      = config.CONSUMER_KEY
CS      = config.CONSUMER_SECRET
AT      = config.ACCESS_TOKEN
ATS     = config.ACCESS_TOKEN_SECRET
auth = tweepy.OAuthHandler(CK, CS)
auth.set_access_token(AT, ATS)
api = tweepy.API(auth)
```

## 🌔日時を取得
`datetime` ライブラリの `datetime.datetime.now()` 関数を用いて、現在時刻を年月日、時ごとに取得します。後で月齢計算に使うので忘れずに `int` 型に型変換しておきます。
（ついでに `dt_now.strftime('%H:%M:%S')` を使って後でユーザーネームに時刻を載せる準備をしています）

```python
import datetime

# 現在の年月日時を int 型で取得
dt_now = datetime.datetime.now()
timenow = dt_now.strftime('%H:%M:%S')
year = int(dt_now.strftime('%Y'))
month = int(dt_now.strftime('%m'))
day = int(dt_now.strftime('%d'))
hour = int(dt_now.strftime('%H'))
```

## 🌕日時に応じて月齢を計算
年月日を用いて月齢を計算します。計算方法は Wikipediaにある [月齢のページ](https://ja.wikipedia.org/wiki/%E6%9C%88%E9%BD%A2) にあるものを使用します（この計算方法自体は 堀源一郎, 1968, 「おに・おに・にし-簡易月齢計算法」『天文月報』を参考にしています）。

これによると各月別に対応する以下の表のような値 `month_const` を定めます。

| 月 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|:--:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 値 | 0 | 2 | 0 | 2 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

この値と、年 `year`, 月 `month`, 日 `day` を用いて

> `moon_age = (((year - 11) % 19) * 11 + month_const + day) % 30`

を計算するとおよそ月齢になるそうです。

### 🌛月別の `month_const` の取得
上記の表を確認すると

1. 五月までの偶数月は `2`, 奇数月は `0`
2. 六月以降は `[X月] - 2`
3. 五月は `2`

というパターンです。よく見ると四月も `[X月] - 2` で表現できるので、 Python に落とし込むと以下のようになります。

```python
# 月ごとの定数の計算
if month == 1 or month == 3:
    month_const = 0
elif month == 2 or month == 5:
    month_const = 2
else:
    month_const = month - 2
```

### 🌛月齢の計算
先に求めた月別の値と年月日を用いて月齢を計算します。また月は夜に出るものなので、深夜の「『日』付」が変わるとともに絵文字を変えるのは不自然な気がしたので、先に取得した現在時刻を用いて、お昼を迎えるまでは前日の月齢を採用するように仕掛けます。

```python
# なんとなくお昼の 12 時以前は前日の情報にしたい
if hour < 12:
    day -= 1

# 月齢を計算し, 月齢ごとに emoji を割り振る
moon_age = (((year - 11) % 19) * 11 + month_const + day) % 30
```

## 🌖月齢に応じて emoji を選択
月齢は `0 - 29` の範囲なのですが、月の絵文字は全部で 8 種類しかないので、適当にそれっぽく割り振りました。いわゆる「満月」は月齢 `13.8 - 15.8` の時を指すようなので、それを基準に月絵文字配列 `moons` から適切な emoji を選択します。

```python
# 各月の絵文字
moons = [
        "🌑", #1 新月
        "🌒", #2 三日月
        "🌓", #3 半月
        "🌔", #4 十三夜月
        "🌕", #5 満月
        "🌖", #6 寝待月
        "🌗", #7 弦月
        "🌘" #8 晦月
]

if moon_age > 2.0 and moon_age <= 4.2:
    moon_num = 1
elif moon_age > 4.2 and moon_age <= 8.4:
    moon_num = 2
elif moon_age > 8.4 and moon_age <= 13.8:
    moon_num = 3
elif moon_age > 13.8 and moon_age <= 15.8:
    moon_num = 4
elif moon_age > 15.8 and moon_age <= 19.0:
    moon_num = 5
elif moon_age > 19.0 and moon_age <= 22.8:
    moon_num = 6
elif moon_age > 22.8 and moon_age <= 26.8:
    moon_num = 7
else:
    moon_num = 0

print("月齢 %d" % moon_age)
moon = moons[moon_num]
```

## 🌗Tweepy を通じて Twitter の名前を変更
ユーザーネーム等の変更は [Tweepy 4.1.0 ドキュメント Twitter API v1.1 リファレンス](https://mkuriki.com/tweepy/api.html) によれば、`update_profile` 関数を以下のように使います。`

> `API.update_profile([name][, url][, location][, description])`
name – ユーザーネーム（最大 20 文字）
url – アドレス（最大 100 文字、"http://" を省略しても勝手につけられる）
location – 場所（最大 30 文字）
description – 自己紹介（最大 160 文字）

引数についてそれぞれ省略したものは、元のものが変更されないようです。
今回はユーザーネーム `name` と自己紹介欄に月 emoji のことを書いておきたいため `description` に引数を与えます。後はこれを適当に実行すれば実行時の月齢 emoji をユーザーネームに反映できます。

```python
# 表示名を設定
nameStr = "村橋究理基%s%s" % (moon, timenow)
profileStr = "名前の%sは当日の月の形を概ね表しています。北海道..." % moon

api.update_profile(name = nameStr, description = profileStr)
```

# 🌘まとめ
ユーザーネームは Twitter の顔😊なので色々試せるといいですね。

## 🌑参考リンク

* [Tweepy 3.9.0 Documentation API Reference](http://docs.tweepy.org/en/latest/api.html)
* [Tweepy ドキュメント 4.1.0](https://mkuriki.com/tweepy/)
* [2020年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説 - ITTI STAFF BLOG](https://www.itti.jp/web-direction/how-to-apply-for-twitter-api/)
* [TweepyでTwitterに投稿する - Qiita](https://qiita.com/pontyo4/items/da8ba576c6281d67c0c7)
* [月齢 - Wikipedia 日本語版](https://ja.wikipedia.org/wiki/%E6%9C%88%E9%BD%A2) 
* [mkuriki1990/MAUT - GitHub](https://github.com/mkuriki1990/maut) : ソースコード
* [Twitter @mkuriki_](https://twitter.com/mkuriki_) : 使用例

以下はソースコード全体。MIT License. 

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

# The MIT License (MIT)
# 
# Copyright (c) 2020 Murahashi Kuriki
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import sys
import io
import tweepy
import datetime
import config
# 日本語を吐き出すとエラーが出るので追加
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

# OAuth認証部分
# 同じディレクトリにある config.py から鍵情報を読み込む
CK      = config.CONSUMER_KEY
CS      = config.CONSUMER_SECRET
AT      = config.ACCESS_TOKEN
ATS     = config.ACCESS_TOKEN_SECRET
auth = tweepy.OAuthHandler(CK, CS)
auth.set_access_token(AT, ATS)
api = tweepy.API(auth)

sc_name = api.get_user('mkuriki_')
print(sc_name.name)

# 各月の絵文字
moons = [
        "🌑", #1 新月
        "🌒", #2 三日月
        "🌓", #3 半月
        "🌔", #4 十三夜月
        "🌕", #5 満月
        "🌖", #6 寝待月
        "🌗", #7 弦月
        "🌘" #8 晦月
]

# 現在の年月日時を int 型で取得
dt_now = datetime.datetime.now()
timenow = dt_now.strftime('%H:%M:%S')
year = int(dt_now.strftime('%Y'))
month = int(dt_now.strftime('%m'))
day = int(dt_now.strftime('%d'))
hour = int(dt_now.strftime('%H'))

# 月ごとの定数の計算
if month == 1 or month == 3:
    month_const = 0
elif month == 2 or month == 5:
    month_const = 2
else:
    month_const = month - 2

# なんとなくお昼の 12 時以前は前日の情報にしたい
if hour < 12:
    day -= 1

# 月齢を計算し, 月齢ごとに emoji を割り振る
moon_age = (((year - 11) % 19) * 11 + month_const + day) % 30

if moon_age > 2.0 and moon_age <= 4.2:
    moon_num = 1
elif moon_age > 4.2 and moon_age <= 8.4:
    moon_num = 2
elif moon_age > 8.4 and moon_age <= 13.8:
    moon_num = 3
elif moon_age > 13.8 and moon_age <= 15.8:
    moon_num = 4
elif moon_age > 15.8 and moon_age <= 19.0:
    moon_num = 5
elif moon_age > 19.0 and moon_age <= 22.8:
    moon_num = 6
elif moon_age > 22.8 and moon_age <= 26.8:
    moon_num = 7
else:
    moon_num = 0

print("月齢 %d" % moon_age)
moon = moons[moon_num]

# 表示名を設定
nameStr = "村橋究理基%s%s" % (moon, timenow)
profileStr = "名前の%sは当日の月の形を概ね表しています。北海道大学理学院宇宙理学専攻/地球惑星科学科 博士3+2年 惑星気象/火星大気シミュレーション。3Dプリンタ/恵迪寮寮歌集アプリ開発/高等学校教諭 専修免許状(理科)/学芸員/恵迪寮第300期寮長/(一社)恵迪寮同窓会理事/愛知県立津島高校出身" % moon

api.update_profile(name = nameStr, description = profileStr)
```