128 lines
4.1 KiB
Python
128 lines
4.1 KiB
Python
import urllib.request
|
||
import urllib.parse
|
||
import json
|
||
import ssl
|
||
from datetime import datetime, timezone, timedelta
|
||
import wmill
|
||
|
||
JST = timezone(timedelta(hours=9))
|
||
|
||
|
||
def main():
|
||
# シークレット取得
|
||
api_key = wmill.get_variable("u/admin/NOTIFICATION_API_KEY")
|
||
line_token = wmill.get_variable("u/admin/LINE_CHANNEL_ACCESS_TOKEN")
|
||
line_to = wmill.get_variable("u/admin/LINE_TO")
|
||
|
||
# 前回実行時刻を取得(初回は現在時刻 - 10分)
|
||
state = wmill.get_state() or {}
|
||
last_checked = state.get("last_checked_at")
|
||
if last_checked:
|
||
since = last_checked
|
||
else:
|
||
since = (datetime.now(JST) - timedelta(minutes=10)).isoformat()
|
||
|
||
print(f"[通知] 変更確認: since={since}")
|
||
|
||
# API呼び出し
|
||
ssl_ctx = ssl.create_default_context()
|
||
ssl_ctx.check_hostname = False
|
||
ssl_ctx.verify_mode = ssl.CERT_NONE
|
||
|
||
params = urllib.parse.urlencode({"since": since})
|
||
url = f"https://shiraou.keinafarm.net/reservations/api/changes/?{params}"
|
||
|
||
req = urllib.request.Request(url, headers={"X-API-Key": api_key})
|
||
with urllib.request.urlopen(req, context=ssl_ctx, timeout=30) as resp:
|
||
data = json.loads(resp.read().decode("utf-8"))
|
||
|
||
checked_at = data["checked_at"]
|
||
reservations = data.get("reservations", [])
|
||
usages = data.get("usages", [])
|
||
|
||
print(f"[通知] checked_at={checked_at}, 予約={len(reservations)}件, 実績={len(usages)}件")
|
||
|
||
# 変更があればLINE通知(エラー時は状態を更新しない)
|
||
if reservations or usages:
|
||
message = _format_message(reservations, usages)
|
||
_send_line(line_token, line_to, message)
|
||
print("[通知] LINE送信完了")
|
||
else:
|
||
print("[通知] 変更なし、通知スキップ")
|
||
|
||
# 正常完了時のみ状態更新
|
||
wmill.set_state({"last_checked_at": checked_at})
|
||
print(f"[通知] last_checked_at更新: {checked_at}")
|
||
|
||
return {
|
||
"since": since,
|
||
"checked_at": checked_at,
|
||
"reservations_count": len(reservations),
|
||
"usages_count": len(usages),
|
||
"notified": bool(reservations or usages),
|
||
}
|
||
|
||
|
||
def _format_message(reservations, usages):
|
||
lines = ["\U0001f4cb 営農システム 変更通知\n"]
|
||
|
||
OP_R = {
|
||
"create": ("\U0001f7e2", "予約作成"),
|
||
"update": ("\U0001f535", "予約変更"),
|
||
"cancel": ("\U0001f534", "予約キャンセル"),
|
||
}
|
||
OP_U = {
|
||
"create": ("\U0001f7e2", "実績登録"),
|
||
"update": ("\U0001f535", "実績修正"),
|
||
"delete": ("\U0001f534", "実績削除"),
|
||
}
|
||
|
||
for r in reservations:
|
||
start = r["start_at"][:16].replace("T", " ")
|
||
end = r["end_at"][:16].replace("T", " ")
|
||
icon, label = OP_R.get(r["operation"], ("\u26aa", r["operation"]))
|
||
lines += [
|
||
f"{icon} {label}",
|
||
f" 機械: {r['machine_name']}",
|
||
f" 利用者: {r['user_name']}",
|
||
f" 日時: {start} \uff5e {end}",
|
||
]
|
||
if r.get("reason"):
|
||
lines.append(f" 理由: {r['reason']}")
|
||
lines.append("")
|
||
|
||
for u in usages:
|
||
start = u["start_at"][:16].replace("T", " ")
|
||
icon, label = OP_U.get(u["operation"], ("\u26aa", u["operation"]))
|
||
lines += [
|
||
f"{icon} {label}",
|
||
f" 機械: {u['machine_name']}",
|
||
f" 利用者: {u['user_name']}",
|
||
f" 利用量: {u['amount']}{u['unit']}",
|
||
f" 日: {start[:10]}",
|
||
]
|
||
if u.get("reason"):
|
||
lines.append(f" 理由: {u['reason']}")
|
||
lines.append("")
|
||
|
||
return "\n".join(lines).strip()
|
||
|
||
|
||
def _send_line(token, to, message):
|
||
payload = json.dumps({
|
||
"to": to,
|
||
"messages": [{"type": "text", "text": message}],
|
||
}).encode("utf-8")
|
||
|
||
req = urllib.request.Request(
|
||
"https://api.line.me/v2/bot/message/push",
|
||
data=payload,
|
||
headers={
|
||
"Authorization": f"Bearer {token}",
|
||
"Content-Type": "application/json",
|
||
},
|
||
method="POST",
|
||
)
|
||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||
return resp.read().decode("utf-8")
|