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")
|
||||||
@@ -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
|
||||||
@@ -5,6 +5,8 @@ locks:
|
|||||||
'f/app_custom/system_heartbeat__flow+step2:_データ検証.py': d7f4e6e04ed116ba3836cb32793a0187a69359a3f2a807b533030b01d42bed39
|
'f/app_custom/system_heartbeat__flow+step2:_データ検証.py': d7f4e6e04ed116ba3836cb32793a0187a69359a3f2a807b533030b01d42bed39
|
||||||
'f/app_custom/system_heartbeat__flow+step3:_httpヘルスチェック.py': 5d3bce0ddb4f521444bf01bc80670e7321933ad09f935044f4d6123c658ca7a8
|
'f/app_custom/system_heartbeat__flow+step3:_httpヘルスチェック.py': 5d3bce0ddb4f521444bf01bc80670e7321933ad09f935044f4d6123c658ca7a8
|
||||||
'f/app_custom/system_heartbeat__flow+step4:_年度判定_&_最終レポート.py': 6889bfac9a629fa42cf0505cbc945ba3782c59e1697b8493ce6101ef5ffa8b32
|
'f/app_custom/system_heartbeat__flow+step4:_年度判定_&_最終レポート.py': 6889bfac9a629fa42cf0505cbc945ba3782c59e1697b8493ce6101ef5ffa8b32
|
||||||
|
f/shiraou/shiraou_notification__flow+__flow_hash: d8268404f9c59e04cbf16e9d05c90412e054b5e9e369e24b070234ca89708b0c
|
||||||
|
f/shiraou/shiraou_notification__flow+変更確認・line通知.py: abf0f2f17f9d4f85677db9f98a5a6368d4cb4a68a9611809afbee00528692b17
|
||||||
g/all/setup_app__app+__app_hash: d71add32e14e552d1a4c861c972a50d9598b07c0af201bbadec5b59bbd99d7e3
|
g/all/setup_app__app+__app_hash: d71add32e14e552d1a4c861c972a50d9598b07c0af201bbadec5b59bbd99d7e3
|
||||||
g/all/setup_app__app+change_account.deno.ts: 3c592cac27e9cdab0de6ae19270bcb08c7fa54355ad05253a12de2351894346b
|
g/all/setup_app__app+change_account.deno.ts: 3c592cac27e9cdab0de6ae19270bcb08c7fa54355ad05253a12de2351894346b
|
||||||
u/admin/hub_sync: aaf9fd803fa229f3029d1bb02bbe3cc422fce680cad39c4eec8dd1da115de102
|
u/admin/hub_sync: aaf9fd803fa229f3029d1bb02bbe3cc422fce680cad39c4eec8dd1da115de102
|
||||||
|
|||||||
Reference in New Issue
Block a user