サーバーからフローを取得
This commit is contained in:
24
flows/git_sync.flow.json
Normal file
24
flows/git_sync.flow.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"path": "u/antigravity/git_sync",
|
||||
"summary": "Git Sync Workflow",
|
||||
"description": "Automatically sync Windmill workflows to Git repository (sync branch)",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "",
|
||||
"type": "rawscript",
|
||||
"content": "#!/bin/bash\nset -e\nexport PATH=/usr/bin:/usr/local/bin:/usr/sbin:/sbin:/bin:$PATH\n\nGREEN=\"\\033[0;32m\"\nYELLOW=\"\\033[1;33m\"\nRED=\"\\033[0;31m\"\nNC=\"\\033[0m\"\n\necho -e \"${GREEN}=== Windmill Workflow Git Sync ===${NC}\"\n\nREPO_ROOT=\"/workspace\"\nWMILL_DIR=\"${REPO_ROOT}/workflows\"\n\nif ! command -v wmill &> /dev/null; then\n echo -e \"${YELLOW}Installing windmill-cli...${NC}\"\n npm install -g windmill-cli\n export PATH=$(npm prefix -g)/bin:$PATH\nfi\n\ngit config --global --add safe.directory \"$REPO_ROOT\"\ngit config --global user.email \"bot@keinafarm.net\"\ngit config --global user.name \"Windmill Bot\"\n\n# sync ブランチを使用\nCURRENT_BRANCH=$(git -C \"$REPO_ROOT\" rev-parse --abbrev-ref HEAD)\nif [ \"$CURRENT_BRANCH\" != \"sync\" ]; then\n echo -e \"${YELLOW}Switching to sync branch...${NC}\"\n git -C \"$REPO_ROOT\" fetch origin sync\n git -C \"$REPO_ROOT\" checkout sync\nfi\n\necho -e \"${YELLOW}Pulling from origin/sync...${NC}\"\ngit -C \"$REPO_ROOT\" pull --rebase origin sync || {\n echo -e \"${RED}Failed to pull from remote. Continuing...${NC}\"\n}\n\necho -e \"${YELLOW}Pulling from Windmill...${NC}\"\ncd \"$WMILL_DIR\"\nwmill sync pull --config-dir /workspace/wmill_config --skip-variables --skip-secrets --skip-resources --yes || exit 1\n\ncd \"$REPO_ROOT\"\nif [[ -n $(git status --porcelain) ]]; then\n echo -e \"${YELLOW}Changes detected, committing to Git...${NC}\"\n git add -A\n TIMESTAMP=$(date \"+%Y-%m-%d %H:%M:%S\")\n git commit -m \"Auto-sync: ${TIMESTAMP}\"\n echo -e \"${YELLOW}Pushing to Gitea (sync branch)...${NC}\"\n git push origin sync || {\n echo -e \"${RED}Failed to push.${NC}\"\n exit 1\n }\n echo -e \"${GREEN}Changes pushed to Gitea (sync branch)${NC}\"\nelse\n echo -e \"${GREEN}No changes detected${NC}\"\nfi\n\necho -e \"${GREEN}=== Sync Complete ===${NC}\"\n",
|
||||
"language": "bash",
|
||||
"input_transforms": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
38
flows/hourly_chime.flow.json
Normal file
38
flows/hourly_chime.flow.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"path": "u/akiracraftwork/hourly_chime",
|
||||
"summary": "鳩時計機能",
|
||||
"description": "",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "{\n \"dependencies\": {}\n}\n//bun.lock\n<empty>",
|
||||
"type": "rawscript",
|
||||
"content": "export async function main(\n device: string = \"オフィスの右エコー\",\n prefix: string = \"現在時刻は\",\n suffix: string = \"です\"\n) {\n const now = new Date();\n const hhmm = new Intl.DateTimeFormat(\"ja-JP\", {\n timeZone: \"Asia/Tokyo\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n }).format(now); // 例: 09:30\n\n const [h, m] = hhmm.split(\":\");\n const text = `${prefix}${Number(h)}時${Number(m)}分${suffix}`;\n\n const res = await fetch(\"http://alexa_api:3500/speak\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ device, text }),\n });\n\n if (!res.ok) {\n const body = await res.text();\n throw new Error(`alexa-api error ${res.status}: ${body}`);\n }\n\n return { ok: true, device, text };\n}\n",
|
||||
"language": "bun",
|
||||
"input_transforms": {
|
||||
"device": {
|
||||
"type": "static",
|
||||
"value": "オフィスの右エコー"
|
||||
},
|
||||
"prefix": {
|
||||
"type": "static",
|
||||
"value": "現在時刻は"
|
||||
},
|
||||
"suffix": {
|
||||
"type": "static",
|
||||
"value": "です"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"properties": {},
|
||||
"required": [],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
24
flows/konnnichiha.flow.json
Normal file
24
flows/konnnichiha.flow.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"path": "f/dev/konnnichiha",
|
||||
"summary": "Print greeting",
|
||||
"description": "",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"content": "def main():\n print('こんにちは、世界')",
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -6,14 +6,14 @@
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"summary": "変更確認・LINE通知",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\nanyio==4.12.1\ncertifi==2026.1.4\nh11==0.16.0\nhttpcore==1.0.9\nhttpx==0.28.1\nidna==3.11\ntyping-extensions==4.15.0\nwmill==1.640.0",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "import urllib.request\nimport urllib.parse\nimport json\nimport ssl\nfrom datetime import datetime, timezone, timedelta\nimport wmill\n\nJST = timezone(timedelta(hours=9))\n\n\ndef main():\n # シークレット取得\n api_key = wmill.get_variable(\"u/admin/NOTIFICATION_API_KEY\")\n line_token = wmill.get_variable(\"u/admin/LINE_CHANNEL_ACCESS_TOKEN\")\n line_to = wmill.get_variable(\"u/admin/LINE_TO\")\n\n # 前回実行時刻を取得(初回は現在時刻 - 10分)\n try:\n last_checked = wmill.get_variable(\"u/admin/SHIRAOU_LAST_CHECKED_AT\")\n if not last_checked:\n last_checked = None\n except Exception:\n last_checked = None\n\n if last_checked:\n since = last_checked\n else:\n since = (datetime.now(JST) - timedelta(minutes=10)).isoformat()\n\n print(f\"[通知] 変更確認: since={since}\")\n\n # API呼び出し\n ssl_ctx = ssl.create_default_context()\n ssl_ctx.check_hostname = False\n ssl_ctx.verify_mode = ssl.CERT_NONE\n\n params = urllib.parse.urlencode({\"since\": since})\n url = f\"https://shiraou.keinafarm.net/reservations/api/changes/?{params}\"\n\n req = urllib.request.Request(url, headers={\"X-API-Key\": api_key})\n with urllib.request.urlopen(req, context=ssl_ctx, timeout=30) as resp:\n data = json.loads(resp.read().decode(\"utf-8\"))\n\n checked_at = data[\"checked_at\"]\n reservations = data.get(\"reservations\", [])\n usages = data.get(\"usages\", [])\n\n print(f\"[通知] checked_at={checked_at}, 予約={len(reservations)}件, 実績={len(usages)}件\")\n\n # 変更があればLINE通知(エラー時は状態を更新しない)\n if reservations or usages:\n message = _format_message(reservations, usages)\n _send_line(line_token, line_to, message)\n print(\"[通知] LINE送信完了\")\n else:\n print(\"[通知] 変更なし、通知スキップ\")\n\n # 正常完了時のみ状態更新\n wmill.set_variable(\"u/admin/SHIRAOU_LAST_CHECKED_AT\", checked_at)\n print(f\"[通知] last_checked_at更新: {checked_at}\")\n\n return {\n \"since\": since,\n \"checked_at\": checked_at,\n \"reservations_count\": len(reservations),\n \"usages_count\": len(usages),\n \"notified\": bool(reservations or usages),\n }\n\n\ndef _format_message(reservations, usages):\n lines = [\"\\U0001f4cb 営農システム 変更通知\\n\"]\n\n OP_R = {\n \"create\": (\"\\U0001f7e2\", \"予約作成\"),\n \"update\": (\"\\U0001f535\", \"予約変更\"),\n \"cancel\": (\"\\U0001f534\", \"予約キャンセル\"),\n }\n OP_U = {\n \"create\": (\"\\U0001f7e2\", \"実績登録\"),\n \"update\": (\"\\U0001f535\", \"実績修正\"),\n \"delete\": (\"\\U0001f534\", \"実績削除\"),\n }\n\n for r in reservations:\n start = r[\"start_at\"][:16].replace(\"T\", \" \")\n end = r[\"end_at\"][:16].replace(\"T\", \" \")\n icon, label = OP_R.get(r[\"operation\"], (\"\\u26aa\", r[\"operation\"]))\n lines += [\n f\"{icon} {label}\",\n f\" 機械: {r['machine_name']}\",\n f\" 利用者: {r['user_name']}\",\n f\" 日時: {start} \\uff5e {end}\",\n ]\n if r.get(\"reason\"):\n lines.append(f\" 理由: {r['reason']}\")\n lines.append(\"\")\n\n for u in usages:\n start = u[\"start_at\"][:16].replace(\"T\", \" \")\n icon, label = OP_U.get(u[\"operation\"], (\"\\u26aa\", u[\"operation\"]))\n lines += [\n f\"{icon} {label}\",\n f\" 機械: {u['machine_name']}\",\n f\" 利用者: {u['user_name']}\",\n f\" 利用量: {u['amount']}{u['unit']}\",\n f\" 日: {start[:10]}\",\n ]\n if u.get(\"reason\"):\n lines.append(f\" 理由: {u['reason']}\")\n lines.append(\"\")\n\n return \"\\n\".join(lines).strip()\n\n\ndef _send_line(token, to, message):\n payload = json.dumps({\n \"to\": to,\n \"messages\": [{\"type\": \"text\", \"text\": message}],\n }).encode(\"utf-8\")\n\n req = urllib.request.Request(\n \"https://api.line.me/v2/bot/message/push\",\n data=payload,\n headers={\n \"Authorization\": f\"Bearer {token}\",\n \"Content-Type\": \"application/json\",\n },\n method=\"POST\",\n )\n with urllib.request.urlopen(req, timeout=30) as resp:\n return resp.read().decode(\"utf-8\")\n",
|
||||
"input_transforms": {},
|
||||
"lock": ""
|
||||
}
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
},
|
||||
"summary": "変更確認・LINE通知"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -6,70 +6,70 @@
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"summary": "Step1: 診断データ生成",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "import uuid\nfrom datetime import datetime, timezone\n\ndef main():\n \"\"\"診断データを生成する\"\"\"\n now = datetime.now(timezone.utc)\n run_id = str(uuid.uuid4())\n check_value = 2 + 2\n \n result = {\n \"timestamp\": now.isoformat(),\n \"run_id\": run_id,\n \"check\": check_value,\n \"python_version\": __import__('sys').version\n }\n print(f\"[Step1] 診断データ生成完了\")\n print(f\" run_id: {run_id}\")\n print(f\" timestamp: {now.isoformat()}\")\n print(f\" check: {check_value}\")\n return result\n",
|
||||
"input_transforms": {},
|
||||
"lock": ""
|
||||
}
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
},
|
||||
"summary": "Step1: 診断データ生成"
|
||||
},
|
||||
{
|
||||
"id": "b",
|
||||
"summary": "Step2: データ検証",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "from datetime import datetime, timezone\n\ndef main(step1_result: dict):\n \"\"\"Step1の結果を検証する\"\"\"\n errors = []\n \n # 計算チェック\n if step1_result.get(\"check\") != 4:\n errors.append(f\"計算エラー: expected 4, got {step1_result.get('check')}\")\n \n # run_idの存在チェック\n if not step1_result.get(\"run_id\"):\n errors.append(\"run_idが存在しない\")\n \n # timestampの存在チェック\n if not step1_result.get(\"timestamp\"):\n errors.append(\"timestampが存在しない\")\n \n if errors:\n error_msg = \"; \".join(errors)\n print(f\"[Step2] 検証失敗: {error_msg}\")\n raise Exception(f\"検証失敗: {error_msg}\")\n \n print(f\"[Step2] データ検証OK\")\n print(f\" 計算チェック: 2+2={step1_result['check']} ✓\")\n print(f\" run_id: {step1_result['run_id']} ✓\")\n print(f\" timestamp: {step1_result['timestamp']} ✓\")\n \n return {\n \"verification\": \"PASS\",\n \"step1_data\": step1_result\n }\n",
|
||||
"language": "python3",
|
||||
"input_transforms": {
|
||||
"step1_result": {
|
||||
"type": "javascript",
|
||||
"expr": "results.a"
|
||||
"expr": "results.a",
|
||||
"type": "javascript"
|
||||
}
|
||||
},
|
||||
"lock": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Step2: データ検証"
|
||||
},
|
||||
{
|
||||
"id": "c",
|
||||
"summary": "Step3: HTTPヘルスチェック",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "import urllib.request\nimport ssl\n\ndef main(verification_result: dict):\n \"\"\"Windmillサーバー自身へのHTTPチェック\"\"\"\n url = \"https://windmill.keinafarm.net/api/version\"\n \n # SSL検証をスキップ(自己署名証明書対応)\n ctx = ssl.create_default_context()\n ctx.check_hostname = False\n ctx.verify_mode = ssl.CERT_NONE\n \n try:\n req = urllib.request.Request(url)\n with urllib.request.urlopen(req, context=ctx, timeout=10) as response:\n status_code = response.status\n body = response.read().decode('utf-8')\n except Exception as e:\n print(f\"[Step3] HTTPチェック失敗: {e}\")\n raise Exception(f\"HTTPヘルスチェック失敗: {e}\")\n \n print(f\"[Step3] HTTPヘルスチェックOK\")\n print(f\" URL: {url}\")\n print(f\" Status: {status_code}\")\n print(f\" Version: {body}\")\n \n return {\n \"http_check\": \"PASS\",\n \"status_code\": status_code,\n \"server_version\": body\n }\n",
|
||||
"language": "python3",
|
||||
"input_transforms": {
|
||||
"verification_result": {
|
||||
"type": "javascript",
|
||||
"expr": "results.b"
|
||||
"expr": "results.b",
|
||||
"type": "javascript"
|
||||
}
|
||||
},
|
||||
"lock": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Step3: HTTPヘルスチェック"
|
||||
},
|
||||
{
|
||||
"id": "d",
|
||||
"summary": "Step4: 年度判定 & 最終レポート",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"language": "python3",
|
||||
"content": "from datetime import datetime, timezone\n\ndef main(step1_data: dict, verification: dict, http_check: dict):\n \"\"\"年度判定と最終診断レポートを生成\"\"\"\n now = datetime.now(timezone.utc)\n \n # 日本の年度判定(4月始まり)\n fiscal_year = now.year if now.month >= 4 else now.year - 1\n \n report = {\n \"status\": \"ALL OK\",\n \"fiscal_year\": fiscal_year,\n \"diagnostics\": {\n \"data_generation\": \"PASS\",\n \"data_verification\": verification.get(\"verification\", \"UNKNOWN\"),\n \"http_health\": http_check.get(\"http_check\", \"UNKNOWN\"),\n \"server_version\": http_check.get(\"server_version\", \"UNKNOWN\")\n },\n \"run_id\": step1_data.get(\"run_id\"),\n \"started_at\": step1_data.get(\"timestamp\"),\n \"completed_at\": now.isoformat()\n }\n \n print(\"\")\n print(\"========================================\")\n print(\" Windmill Heartbeat - 診断レポート\")\n print(\"========================================\")\n print(f\" Status: {report['status']}\")\n print(f\" 年度: {fiscal_year}年度\")\n print(f\" Run ID: {report['run_id']}\")\n print(f\" Server: {report['diagnostics']['server_version']}\")\n print(f\" 開始: {report['started_at']}\")\n print(f\" 完了: {report['completed_at']}\")\n print(\" ────────────────────────────────────\")\n print(f\" データ生成: PASS ✓\")\n print(f\" データ検証: {report['diagnostics']['data_verification']} ✓\")\n print(f\" HTTP確認: {report['diagnostics']['http_health']} ✓\")\n print(\"========================================\")\n print(\"\")\n \n return report\n",
|
||||
"language": "python3",
|
||||
"input_transforms": {
|
||||
"http_check": {
|
||||
"expr": "results.c",
|
||||
"type": "javascript"
|
||||
},
|
||||
"step1_data": {
|
||||
"type": "javascript",
|
||||
"expr": "results.a"
|
||||
"expr": "results.a",
|
||||
"type": "javascript"
|
||||
},
|
||||
"verification": {
|
||||
"type": "javascript",
|
||||
"expr": "results.b"
|
||||
},
|
||||
"http_check": {
|
||||
"type": "javascript",
|
||||
"expr": "results.c"
|
||||
"expr": "results.b",
|
||||
"type": "javascript"
|
||||
}
|
||||
},
|
||||
"lock": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Step4: 年度判定 & 最終レポート"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
24
flows/textout.flow.json
Normal file
24
flows/textout.flow.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"path": "f/dev/textout",
|
||||
"summary": "Display current time on startup",
|
||||
"description": "",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\n",
|
||||
"type": "rawscript",
|
||||
"content": "def main():\n from datetime import datetime\n print(datetime.now().strftime('%H:%M:%S'))",
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
27
flows/weather_sync.flow.json
Normal file
27
flows/weather_sync.flow.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"path": "f/weather/weather_sync",
|
||||
"summary": "Weather Sync - 気象データ日次同期",
|
||||
"description": "Open-Meteo から昨日の気象データを取得し、Keinasystem DB に保存する。毎朝6時実行。",
|
||||
"value": {
|
||||
"modules": [
|
||||
{
|
||||
"id": "a",
|
||||
"value": {
|
||||
"lock": "# py: 3.12\nanyio==4.12.1\ncertifi==2026.2.25\ncharset-normalizer==3.4.4\nh11==0.16.0\nhttpcore==1.0.9\nhttpx==0.28.1\nidna==3.11\nrequests==2.32.5\ntyping-extensions==4.15.0\nurllib3==2.6.3\nwmill==1.646.0",
|
||||
"type": "rawscript",
|
||||
"content": "import wmill\nimport requests\nimport datetime\n\nLATITUDE = 33.213\nLONGITUDE = 133.133\nTIMEZONE = \"Asia/Tokyo\"\n\nOPEN_METEO_URL = \"https://archive-api.open-meteo.com/v1/archive\"\nDAILY_VARS = [\n \"temperature_2m_mean\",\n \"temperature_2m_max\",\n \"temperature_2m_min\",\n \"sunshine_duration\",\n \"precipitation_sum\",\n \"wind_speed_10m_max\",\n \"surface_pressure_min\",\n]\n\n\ndef main():\n api_key = wmill.get_variable(\"u/admin/KEINASYSTEM_API_KEY\")\n base_url = wmill.get_variable(\"u/admin/KEINASYSTEM_API_URL\").rstrip(\"/\")\n sync_url = f\"{base_url}/api/weather/sync/\"\n\n yesterday = (datetime.date.today() - datetime.timedelta(days=1)).isoformat()\n print(f\"Fetching weather data for {yesterday} ...\")\n\n params = {\n \"latitude\": LATITUDE,\n \"longitude\": LONGITUDE,\n \"start_date\": yesterday,\n \"end_date\": yesterday,\n \"daily\": DAILY_VARS,\n \"timezone\": TIMEZONE,\n }\n resp = requests.get(OPEN_METEO_URL, params=params, timeout=30)\n if resp.status_code != 200:\n raise Exception(f\"Open-Meteo API error: {resp.status_code} {resp.text[:300]}\")\n\n daily = resp.json().get(\"daily\", {})\n dates = daily.get(\"time\", [])\n if not dates:\n print(\"No data returned from Open-Meteo.\")\n return {\"status\": \"no_data\"}\n\n sunshine_raw = daily.get(\"sunshine_duration\", [])\n records = []\n for i, d in enumerate(dates):\n sun_sec = sunshine_raw[i]\n records.append({\n \"date\": d,\n \"temp_mean\": daily[\"temperature_2m_mean\"][i],\n \"temp_max\": daily[\"temperature_2m_max\"][i],\n \"temp_min\": daily[\"temperature_2m_min\"][i],\n \"sunshine_h\": round(sun_sec / 3600, 2) if sun_sec is not None else None,\n \"precip_mm\": daily[\"precipitation_sum\"][i],\n \"wind_max\": daily[\"wind_speed_10m_max\"][i],\n \"pressure_min\": daily[\"surface_pressure_min\"][i],\n })\n\n headers = {\n \"X-API-Key\": api_key,\n \"Content-Type\": \"application/json\",\n }\n post_resp = requests.post(sync_url, json=records, headers=headers, timeout=30)\n if post_resp.status_code not in (200, 201):\n raise Exception(f\"Keinasystem sync error: {post_resp.status_code} {post_resp.text[:300]}\")\n\n result = post_resp.json()\n print(f\"Sync complete: {result}\")\n return result\n",
|
||||
"language": "python3",
|
||||
"input_transforms": {}
|
||||
},
|
||||
"summary": "気象データ取得・同期"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"order": [],
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user