#はじめに
Googleカレンダーとても便利ですよね.Gmailと連動できるし,PCから開けるし,シンプルだし.ただ,不満に思う点があります.それがバイトの給料を計算してくれないことです.シフト管理アプリと連動してくれれば良いのに...
と言うことで,気になってた各種APIとかAWSとかを初心者なりに使いつつ,作ってみました.
#使ったもの
- Slack
- AWS
- Googleカレンダー
- Python
#アーキテクチャ全体像
以下のような構成でbotを作成しました.

#手順
- Slack APIのサイト上でbot作成用のアプリを作成する
- AWS上にSlackイベントを受け取るエンドポイントを作成する
- GoogleAPIを使って,Googleカレンダーからバイトの給料を計算するプログラムを作成
- AWS lambdaに導入
#【手順1】【手順2】Slack botを作成し,AWS上にエンドポイントを作成する
この手順は,以下の記事にとてもお世話になりました.記事の通りに進めると,問題なくできると思います.
AWS初心者でもわかる! ブラウザ上で完結! AWS+Slack Event APIを使ったSlackボット超入門
#【手順3】Googleカレンダーからバイトの給料を計算するプログラムを作成
Googleカレンダーの予定をPythonを使って取得する方法は以下の記事を参考にしました.
Google Calenderの予定をPython3から取得する
次に,公式のサンプルと公式リファレンスを参考に,給料を計算するプログラムを作っていきます.
プログラムは大きく以下に分かれます.
- handle_slack_event():エントリポイント
- MakePayMsg():ユーザテキストに対応した給料を計算し,メッセージを作成
- CalculatePay():日給を計算
フルコードはgithubにあるので,そちらを参照してください.
handle_slack_event()
エントリポイントです.ユーザが送信したテキストを解析し,それを元にメッセージを投稿します.
# -----エントリポイント-----
def handle_slack_event(slack_event, context):
# 受け取ったイベント情報をCloud Watchログに出力
logging.info(json.dumps(slack_event))
# Event APIの認証
if "challenge" in slack_event:
return slack_event.get("challenge")
# ボットによるイベントまたはメッセージ投稿イベント以外の場合
# 反応させないためにそのままリターンする
# Slackには何かしらのレスポンスを返す必要があるのでOKと返す
# (返さない場合、失敗とみなされて同じリクエストが何度か送られてくる)
if is_bot(slack_event) or not is_message_event(slack_event):
return "OK"
# ユーザからのメッセージテキストを取り出す
text = slack_event.get("event").get("text")
# 給料計算クラスの宣言
pay_msg = MakePayMsg()
# ユーザからのテキストを解析して,メッセージを作成
if 'help' in text:
msg = '知りたい情報に対応する番号を入力してください!\n'
msg += '(1)来月の給料\n'
msg += '(2)今年の給料\n'
msg += '(3)給料のログ\n'
elif text == '1':
msg = '来月の給料は¥{}です!'.format(pay_msg.monthpay())
elif text == '2':
msg = '{}'.format(pay_msg.yearpay())
elif text == '3':
msg = '給料ログ\n{}'.format(pay_msg.paylog())
else:
msg = '\\クエー/'
# メッセージの投稿
post_message_to_slack_channel(msg, slack_event.get("event").get("channel"))
# メッセージの投稿とは別に、Event APIによるリクエストの結果として
# Slackに何かしらのレスポンスを返す必要があるのでOKと返す
# (返さない場合、失敗とみなされて同じリクエストが何度か送られてくる)
return "OK"
MakePayMsg()
ユーザテキストに対応した給料を計算し,メッセージを作成します.
# -----給料を計算し,メッセージを作成する-----
class MakePayMsg():
def __init__(self):
self.SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
self.now = datetime.datetime.now()
self.events = self.get_event() # Googleカレンダーから取り出したイベント
self.pay_log = self.make_paylog() # 今年分の給料ログ
# ---Googleカレンダーからイベントを取り出す---
def get_event(self):
creds = None
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', self.SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('/tmp/token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('calendar', 'v3', credentials=creds)
# バイトのシフトを登録しているカレンダーを選択
calender_id = os.environ['CALENDER_ID']
page_token = None
events = service.events().list(calendarId=calender_id, pageToken=page_token).execute()
return events
# ---今年分の給料ログを作成する---
def make_paylog(self):
pay_log = []
cal = CalculatePay(1013, 1063, 1.25, 22) # 時給情報を入力
# eventからバイトの開始時間と終了時間を取り出し,給料計算する
for event in self.events['items']:
# 開始時間と終了時間をdatetimeに変形
stime = event['start']['dateTime']
stime = datetime.datetime(
int(stime[0:4]), int(stime[5:7]), int(stime[8:10]),
int(stime[11:13]), int(stime[14:16]))
etime = event['end']['dateTime']
etime = datetime.datetime(
int(etime[0:4]), int(etime[5:7]), int(etime[8:10]),
int(etime[11:13]), int(etime[14:16]))
# 給料計算をする期間
# (x-1)年12月~x年11月に働いた分がx年の給料
if self.now.month != 12:
sdate = datetime.date(self.now.year-1, 12, 1)
edate = datetime.date(self.now.year, 11, 30)
else:
sdate = datetime.date(self.now.year, 12, 1)
edate = datetime.date(self.now.year+1, 11, 30)
# 1年分の給料をログとして記録
if (stime.date() >= sdate) and (etime.date() <= edate):
# 開始時間と終了時間から1日分の給料計算
daypay = cal.calculate(stime, etime)
# 働いた分が翌月の給料になるように調整
if stime.month==12:
daypay_dir = {'date':stime.date(), 'month':1, 'pay':daypay}
else:
daypay_dir = {'date':stime.date(), 'month':stime.month+1, 'pay':daypay}
pay_log += [daypay_dir]
pay_log = sorted(pay_log, key=lambda x:x['date'])
return pay_log
# ---来月の給料を表示するメッセージを作成---
def monthpay(self):
mpay = 0
for i in self.pay_log:
if self.now.month!=12:
if i['month'] == (self.now.month+1):
mpay += i['pay']
else:
if i['month'] == 1:
mpay += i['pay']
return mpay
# ---1年分の給料を表示するメッセージを作成---
def yearpay(self):
mpay_list = [0] * 12
for i in self.pay_log:
mpay_list[i['month']-1] += i['pay']
msg = ''
for i, mpay in enumerate(mpay_list):
msg += '{}月 ¥{:,}\n'.format(i+1, mpay)
msg += '\n合計¥{}'.format(sum(mpay_list))
return msg
# ---1年分のログを表示するメッセージを作成---
def paylog(self):
msg = ''
month = 0
for i in self.pay_log:
while i['month'] != month:
msg += '\n{}月\n'.format(month+1)
month += 1
msg += '{} ¥{:,}\n'.format(i['date'], i['pay'])
return msg
ここで注意点ですが,Lambda が書き込みできるのは/tmp
配下のファイルのみです.
そのため,あるファイルへの書き込み処理をする場合,ローカルで実行した時にはエラーが起きなかったのに,Lambdaで実行したところ[Errno 30] Read-only file system
が発生すると言うことがあります.
このプログラムでもエラーが出たため,以下のように変更しました.
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
with open('/tmp/token.pickle', 'wb') as token:
pickle.dump(creds, token)
CalculatePay()
日給を計算します.
# -----日給を計算する-----
class CalculatePay():
def __init__(
self, basic_pay, irregular_pay, night_rate, night_time):
self.basic_pay = basic_pay # 平日の時給
self.irregular_pay = irregular_pay # 土日祝日の時給
self.night_rate = night_rate # 深夜給の増額率
self.night_time = night_time # 深夜給になる時間
# ---日給を計算---
def calculate(self, stime, etime):
night_time = datetime.datetime(stime.year, stime.month, stime.day, self.night_time)
if stime.weekday() >= 5 or jpholiday.is_holiday(stime.date()):
pay = self.irregular_pay
else:
pay = self.basic_pay
if etime >= night_time:
normal_time = self.get_h(night_time - stime)
night_time = self.get_h(etime - night_time)
daypay = normal_time * pay + night_time * (pay * self.night_rate)
else:
normal_time = self.get_h(etime - stime)
daypay = normal_time * pay
return round(daypay)
# ---x時間y分→h時間表示に変換---
def get_h(self, delta_time):
h = delta_time.seconds / 3600
return h
【手順4】AWS lambdaに導入
Googleカレンダーから給料を計算するプログラムを書く上で,いくつかのモジュールをインストールしました.
何もせずにAWS Lambdaでこのプログラムを実行すると,ModuleNotFoundErrorが出ます.
様々なモジュールををAWS Lambda上でも使用できるようにするために,Python用のAWS Lambdaデプロイパッケージを作成します.簡潔に言うと,プロジェクトディレクトリにすべての依存モジュールをインストールし,実行ファイルと一緒にzipでアップロードします.
これは,以下のサイトの通りに実行すれば良いです.
【Python】AWS Lambdaで外部モジュールを使用する
実行結果
最近Googleカレンダーにバイトのシフトを記録し始めたので,データが少ないです.
このbotを導入したので,今後はシフト管理もGoogleカレンダーでしていきたいと思います.
(バイト先がどこかわかりやすいアイコンだなあ)
helpの表示

来月の給料の表示

今年の給料

給料のログ

その他
