Auto-sync: 2026-02-21 06:30:12

This commit is contained in:
Windmill Bot
2026-02-21 06:30:12 +00:00
parent 1180d86091
commit 1876548656
5 changed files with 166 additions and 0 deletions

View 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: []

View File

@@ -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

View 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")

View File

@@ -0,0 +1,10 @@
args: {}
cron_version: v2
email: akiracraftwork@gmail.com
enabled: true
is_flow: true
no_flow_overlap: false
schedule: 0 */5 * * * *
script_path: f/shiraou/shiraou_notification
timezone: Asia/Tokyo
ws_error_handler_muted: false