Auto-sync: 2026-02-21 06:30:12
This commit is contained in:
18
workflows/f/shiraou/shiraou_notification__flow/flow.yaml
Normal file
18
workflows/f/shiraou/shiraou_notification__flow/flow.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
summary: 白皇集落営農 変更通知
|
||||
description: shiraou.keinafarm.net の予約・実績変更をポーリングし、変更があればLINEで管理者に通知する。5分毎に実行。
|
||||
value:
|
||||
modules:
|
||||
- id: a
|
||||
summary: 変更確認・LINE通知
|
||||
value:
|
||||
type: rawscript
|
||||
content: '!inline 変更確認・line通知.py'
|
||||
input_transforms: {}
|
||||
lock: '!inline 変更確認・line通知.lock'
|
||||
language: python3
|
||||
schema:
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema'
|
||||
type: object
|
||||
order: []
|
||||
properties: {}
|
||||
required: []
|
||||
@@ -0,0 +1,9 @@
|
||||
# py: 3.12
|
||||
anyio==4.12.1
|
||||
certifi==2026.1.4
|
||||
h11==0.16.0
|
||||
httpcore==1.0.9
|
||||
httpx==0.28.1
|
||||
idna==3.11
|
||||
typing-extensions==4.15.0
|
||||
wmill==1.640.0
|
||||
127
workflows/f/shiraou/shiraou_notification__flow/変更確認・line通知.py
Normal file
127
workflows/f/shiraou/shiraou_notification__flow/変更確認・line通知.py
Normal file
@@ -0,0 +1,127 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user