ken_nogi/NodeSrv/apps/healthcheck-survey-bot/workflows/hc-wa-lineworks-response.json
Kenichiro NOGI 3dccdefdcb fix(hc-wa-lineworks-response): 認証チェーンの分岐前無条件実行と暗黙ハングを解消
Botマスタ取得/JWTクレーム組み立て/Sign JWT/アクセストークン取得をSwitch分岐前の
トランクから外し、直接LINEWORKS API呼び出しを行う4箇所(追加案内送信/日付エラー
送信/ファイル未着送信/LINEWORKSファイルダウンロード)それぞれの直前に複製配置。
待機状態取得0件時とSwitch未一致時に無応答でハングする経路には、明示的な
エラーRespond to Webhookへ誘導するIFノード/フォールバック出力を追加した。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 17:49:02 +09:00

2230 lines
68 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"name": "HC-WA: LINEWORKS応答受信",
"nodes": [
{
"id": "webhook-lineworks-response",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
200,
500
],
"webhookId": "healthcheck-lineworks-response",
"parameters": {
"httpMethod": "POST",
"path": "healthcheck-lineworks-response",
"responseMode": "responseNode",
"options": {
"rawBody": true
}
}
},
{
"id": "dt-get-lineworks_bot_secret",
"name": "LINEWORKS_BOT_SECRET取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
420,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "bNkadTyDgDepYx2p"
},
"filters": {
"conditions": [
{
"keyName": "configKey",
"keyValue": "LINEWORKS_BOT_SECRET"
}
]
},
"returnAll": true
}
},
{
"id": "code-verify-signature",
"name": "署名検証・送信者解決",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
640,
500
],
"parameters": {
"jsCode": "// Codeード「署名検証・送信者解決」の中身\nconst crypto = require(\"crypto\");\nfunction normalizeSignature(value) {\n return String(value || \"\").trim().replace(/^sha256=/i, \"\");\n}\nfunction safeEqual(a, b) {\n const ab = Buffer.from(String(a), \"utf8\");\n const bb = Buffer.from(String(b), \"utf8\");\n if (ab.length !== bb.length) return false;\n return crypto.timingSafeEqual(ab, bb);\n}\nfunction verifySignature(rawBody, headerSignature, botSecret) {\n const headerSig = normalizeSignature(headerSignature);\n if (!headerSig || !botSecret) return false;\n const payload = Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(String(rawBody), \"utf8\");\n const expected = crypto.createHmac(\"sha256\", botSecret).update(payload).digest(\"base64\");\n return safeEqual(headerSig, expected);\n}\n\nconst LINEWORKS_BOT_SECRET = $('LINEWORKS_BOT_SECRET取得').item.json.configValue;\n\nconst item = $input.first();\nconst headers = item.json.headers || {};\nconst rawBody = item.binary && item.binary.data\n ? Buffer.from(item.binary.data.data, \"base64\")\n : Buffer.from(JSON.stringify(item.json.body || {}), \"utf8\");\n\nif (!verifySignature(rawBody, headers[\"x-works-signature\"], LINEWORKS_BOT_SECRET)) {\n throw new Error(\"Unauthorized: signature mismatch\");\n}\n\nconst body = item.json.body || {};\nconst source = body.source || {};\nconst content = body.content || {};\n\nreturn [{\n json: {\n targetEmail: source.userId,\n contentType: content.type,\n text: content.type === \"text\" ? content.text : null,\n fileId: content.type === \"file\" ? content.fileId : null,\n },\n}];"
}
},
{
"id": "dt-get-pleasanter_base_url_prod",
"name": "PLEASANTER_BASE_URL取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
860,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "bNkadTyDgDepYx2p"
},
"filters": {
"conditions": [
{
"keyName": "configKey",
"keyValue": "PLEASANTER_BASE_URL_PROD"
}
]
},
"returnAll": true
}
},
{
"id": "dt-get-pleasanter_api_key_prod",
"name": "PLEASANTER_API_KEY取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
1080,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "bNkadTyDgDepYx2p"
},
"filters": {
"conditions": [
{
"keyName": "configKey",
"keyValue": "PLEASANTER_API_KEY_PROD"
}
]
},
"returnAll": true
}
},
{
"id": "dt-get-healthcheck_site_id",
"name": "HEALTHCHECK_SITE_ID取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
1300,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "bNkadTyDgDepYx2p"
},
"filters": {
"conditions": [
{
"keyName": "configKey",
"keyValue": "HEALTHCHECK_SITE_ID"
}
]
},
"returnAll": true
}
},
{
"id": "http-target-userid-resolve",
"name": "対象者UserId解決",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1520,
500
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/users/get",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue, View: { ApiGetMailAddresses: true } }) }}",
"options": {}
}
},
{
"id": "code-identify-user",
"name": "対象者UserId特定",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1740,
500
],
"parameters": {
"jsCode": "function extractMailAddresses(user) {\n if (!user) return [];\n if (user.MailAddress) return [user.MailAddress];\n if (Array.isArray(user.MailAddresses)) return user.MailAddresses;\n return [];\n}\n\nconst targetEmail = $('署名検証・送信者解決').item.json.targetEmail;\nconst responseData = $('対象者UserId解決').item.json.Response.Data;\n// 実機未検証: /api/users/get をWhere無しで呼んだ場合のレスポンス形状(配列直下 / .Value配下 / 単一オブジェクト)を防御的に吸収する\nconst candidates = Array.isArray(responseData)\n ? responseData\n : (responseData && Array.isArray(responseData.Value) ? responseData.Value : [responseData]);\n\nconst matchedUser = candidates.find((u) => extractMailAddresses(u).includes(targetEmail));\nif (!matchedUser) {\n throw new Error(\"Pleasanterユーザーが見つかりません: \" + targetEmail);\n}\n\nreturn [{\n json: {\n ...$('署名検証・送信者解決').item.json,\n pleasanterUserId: matchedUser.UserId,\n },\n}];"
}
},
{
"id": "http-target-record-search",
"name": "対象レコード検索",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1960,
500
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('HEALTHCHECK_SITE_ID取得').item.json.configValue }}/get",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue, View: { ColumnFilterHash: { ClassC: String($('対象者UserId特定').item.json.pleasanterUserId) }, ColumnFilterSearchTypes: { ClassC: 'ExactMatch' } } }) }}",
"options": {}
}
},
{
"id": "code-pick-record",
"name": "対象レコード選定",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2180,
500
],
"parameters": {
"jsCode": "const responseData = $('対象レコード検索').item.json.Response.Data;\n// 実機未検証: SiteIdでの/get呼び出しレスポンスは.Value配下に配列が入る想定(公式マニュアルのサイト一覧系レスポンス形状)。単一オブジェクトの場合も防御的に吸収する\nconst records = Array.isArray(responseData)\n ? responseData\n : (responseData && Array.isArray(responseData.Value) ? responseData.Value : []);\n\nconst filtered = records.filter((r) => r.Status !== 900 && r.Status !== 910);\nconst sorted = filtered.slice().sort((a, b) => new Date(b.UpdatedTime) - new Date(a.UpdatedTime));\n\nif (sorted.length !== 1) {\n throw new Error(\"対象レコードが一意に定まりません(候補\" + sorted.length + \"件)。手動確認が必要です。\");\n}\n\nconst record = sorted[0];\n\nreturn [{\n json: {\n ...$('対象者UserId特定').item.json,\n resultId: record.ResultId,\n currentStatus: record.Status,\n },\n}];"
}
},
{
"id": "dt-get-bot-state",
"name": "待機状態取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
2400,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "jqMDa2YZTI4f0iQ7"
},
"filters": {
"conditions": [
{
"keyName": "resultId",
"keyValue": "={{ String($('対象レコード選定').item.json.resultId) }}"
}
]
},
"returnAll": true
},
"alwaysOutputData": true
},
{
"id": "http-site-settings-get",
"name": "サイト設定取得",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2620,
500
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('HEALTHCHECK_SITE_ID取得').item.json.configValue }}/getsite",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue }) }}",
"options": {}
}
},
{
"id": "dt-get-lw_bot_client_id",
"name": "LW_BOT_CLIENT_ID取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
2840,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "bNkadTyDgDepYx2p"
},
"filters": {
"conditions": [
{
"keyName": "configKey",
"keyValue": "LW_BOT_CLIENT_ID"
}
]
},
"returnAll": true
}
},
{
"id": "dt-get-lw_bot_client_secret",
"name": "LW_BOT_CLIENT_SECRET取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
3060,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "bNkadTyDgDepYx2p"
},
"filters": {
"conditions": [
{
"keyName": "configKey",
"keyValue": "LW_BOT_CLIENT_SECRET"
}
]
},
"returnAll": true
}
},
{
"id": "dt-get-lw_bot_service_account",
"name": "LW_BOT_SERVICE_ACCOUNT取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
3280,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "bNkadTyDgDepYx2p"
},
"filters": {
"conditions": [
{
"keyName": "configKey",
"keyValue": "LW_BOT_SERVICE_ACCOUNT"
}
]
},
"returnAll": true
}
},
{
"id": "dt-get-lineworks_bot_master_site_id",
"name": "LINEWORKS_BOT_MASTER_SITE_ID取得",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
3500,
500
],
"parameters": {
"operation": "get",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "bNkadTyDgDepYx2p"
},
"filters": {
"conditions": [
{
"keyName": "configKey",
"keyValue": "LINEWORKS_BOT_MASTER_SITE_ID"
}
]
},
"returnAll": true
}
},
{
"id": "http-bot-master-get",
"name": "Botマスタ取得",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
6140,
170
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('LINEWORKS_BOT_MASTER_SITE_ID取得').item.json.configValue }}/getsite",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue }) }}",
"options": {}
}
},
{
"id": "code-jwt-claims",
"name": "JWTクレーム組み立て",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
6360,
170
],
"parameters": {
"jsCode": "const now = Math.floor(Date.now() / 1000);\nconst jwtClaims = JSON.stringify({\n iss: $('LW_BOT_CLIENT_ID取得').item.json.configValue,\n sub: $('LW_BOT_SERVICE_ACCOUNT取得').item.json.configValue,\n iat: now,\n exp: now + 3600,\n aud: 'https://auth.worksmobile.com/oauth2/v2.0/token',\n});\n\n// --- Bot ID解決 ---\n// TODO(Task 12): 484184(LINEWORKS_BOT_MASTER_SITE_ID)の実データ構造は未調査。Task 9(HC-SUB)と同じ暫定実装を踏襲する。\nconst botMasterSiteSettings = $('Botマスタ取得').item.json.Response.Data.SiteSettings;\nconst candidateBotIds = botMasterSiteSettings.BotIds\n || (botMasterSiteSettings.Processes || []).map((p) => p.BotId).filter(Boolean);\nconst botId = candidateBotIds && candidateBotIds[0];\nif (!botId) {\n throw new Error('BotIdを484184(LINEWORKS_BOT_MASTER_SITE_ID)から解決できませんでした。Task 12でBot選択ロジックを実装してください。');\n}\n\nconst targetEmail = $('署名検証・送信者解決').item.json.targetEmail;\nconst apiUrl = 'https://www.worksapis.com/v1.0/bots/' + botId + '/users/' + targetEmail + '/messages';\n\nreturn [{\n json: {\n jwtClaims,\n apiUrl,\n botId,\n targetEmail,\n },\n}];"
}
},
{
"id": "jwt-sign",
"name": "Sign JWT",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
6580,
170
],
"parameters": {
"operation": "sign",
"useJson": true,
"claimsJson": "={{ $json.jwtClaims }}",
"options": {
"algorithm": "RS256"
}
},
"credentials": {
"jwtAuth": {
"id": "Hw0qlEaGfLPnQWp1",
"name": "LINEWORKS Bot Private Key (v4)"
}
}
},
{
"id": "http-access-token",
"name": "アクセストークン取得",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
6800,
170
],
"parameters": {
"method": "POST",
"url": "https://auth.worksmobile.com/oauth2/v2.0/token",
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "assertion",
"value": "={{ $json.token }}"
},
{
"name": "grant_type",
"value": "urn:ietf:params:oauth:grant-type:jwt-bearer"
},
{
"name": "client_id",
"value": "={{ $('LW_BOT_CLIENT_ID取得').item.json.configValue }}"
},
{
"name": "client_secret",
"value": "={{ $('LW_BOT_CLIENT_SECRET取得').item.json.configValue }}"
},
{
"name": "scope",
"value": "bot"
}
]
},
"options": {}
}
},
{
"id": "switch-await-input",
"name": "awaitInput分岐",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [
4600,
500
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $('待機状態取得').item.json.awaitInput }}",
"rightValue": "none",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
},
"outputKey": "none"
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $('待機状態取得').item.json.awaitInput }}",
"rightValue": "date",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
},
"outputKey": "date"
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $('待機状態取得').item.json.awaitInput }}",
"rightValue": "file",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
},
"outputKey": "file"
}
]
},
"options": {
"fallbackOutput": "extra"
}
}
},
{
"id": "code-match-text",
"name": "テキスト照合",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4820,
200
],
"parameters": {
"jsCode": "const stateItem = $('待機状態取得').item.json;\nconst text = $('署名検証・送信者解決').item.json.text;\nlet pendingProcesses = [];\ntry {\n pendingProcesses = JSON.parse(stateItem.pendingProcesses || '[]');\n} catch (e) {\n pendingProcesses = [];\n}\n\nconst trimmed = String(text ?? '').trim();\n// Task 5 matchProcessByLabel相当(pendingProcessesの保存形状に合わせ p.label で照合)\nconst matched = pendingProcesses.find((p) => p.label === trimmed) || null;\n\nreturn [{\n json: {\n resultId: stateItem.resultId,\n matched,\n },\n}];"
}
},
{
"id": "if-text-matched",
"name": "テキスト一致判定",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
5040,
200
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "cond-1",
"leftValue": "={{ !!$('テキスト照合').item.json.matched }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "code-classify-await-input",
"name": "入力待ち種別判定",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5260,
100
],
"parameters": {
"jsCode": "function getValidationColumnNames(process) {\n if (!Array.isArray(process.ValidateInputs)) return [];\n return process.ValidateInputs.map((v) => v.ColumnName).filter(Boolean);\n}\nfunction classifyAwaitInput(process) {\n const columnNames = getValidationColumnNames(process);\n if (columnNames.length === 0) {\n return { awaitInput: \"none\", column: null };\n }\n const column = columnNames[0];\n if (column.startsWith(\"Date\")) {\n return { awaitInput: \"date\", column };\n }\n if (column.startsWith(\"Attachments\")) {\n return { awaitInput: \"file\", column };\n }\n return { awaitInput: \"none\", column: null };\n}\n\nconst matched = $('テキスト照合').item.json.matched;\nconst resultId = $('テキスト照合').item.json.resultId;\n\n// 注意: HC-SUBの「選択肢組み立て」がData Tableに保存するpendingProcessesは\n// {processId, label, tooltip}のみでValidateInputsを持たないため、ここではサイト設定を\n// 再取得してProcessId一致でValidateInputsを引き直す(Task9側の保存形状を変更しない前提の対応)\nconst siteSettings = $('サイト設定取得').item.json.Response.Data.SiteSettings;\nconst processes = siteSettings.Processes || [];\nconst fullProcess = processes.find((p) => p.Id === matched.processId);\nif (!fullProcess) {\n throw new Error('ProcessId ' + matched.processId + ' がサイト設定内に見つかりません');\n}\n\nconst classification = classifyAwaitInput(fullProcess);\n\nreturn [{\n json: {\n resultId,\n processId: matched.processId,\n awaitInput: classification.awaitInput,\n awaitColumn: classification.column,\n },\n}];"
}
},
{
"id": "if-immediate-run",
"name": "即時実行判定",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
5480,
100
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "cond-1",
"leftValue": "={{ $('入力待ち種別判定').item.json.awaitInput === 'none' }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "exec-hc-sub-immediate",
"name": "Process実行(即時)",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
5700,
30
],
"parameters": {
"workflowId": {
"__rl": true,
"mode": "id",
"value": "XRqcykbG2LuAjGG2"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"resultId": "={{ $('入力待ち種別判定').item.json.resultId }}",
"processId": "={{ $('入力待ち種別判定').item.json.processId }}"
}
}
}
},
{
"id": "dt-update-state-await",
"name": "待機状態保存(入力待ち)",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
5700,
170
],
"parameters": {
"operation": "update",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "jqMDa2YZTI4f0iQ7"
},
"filters": {
"conditions": [
{
"keyName": "resultId",
"keyValue": "={{ String($('入力待ち種別判定').item.json.resultId) }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"awaitInput": "={{ $('入力待ち種別判定').item.json.awaitInput }}",
"awaitProcessId": "={{ String($('入力待ち種別判定').item.json.processId) }}",
"awaitColumn": "={{ $('入力待ち種別判定').item.json.awaitColumn }}"
},
"schema": [
{
"id": "awaitInput",
"displayName": "awaitInput",
"required": false,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "awaitProcessId",
"displayName": "awaitProcessId",
"required": false,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "awaitColumn",
"displayName": "awaitColumn",
"required": false,
"type": "string",
"canBeUsedToMatch": true
}
]
}
}
},
{
"id": "code-prompt-message",
"name": "追加案内メッセージ組み立て",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5920,
170
],
"parameters": {
"jsCode": "const awaitInput = $('入力待ち種別判定').item.json.awaitInput;\nconst text = awaitInput === 'date'\n ? '日付を入力してください(例: 2026-03-01 や 令和6年3月1日'\n : 'ファイルを送信してください';\n\nreturn [{ json: { messageContent: { type: 'text', text } } }];"
}
},
{
"id": "http-prompt-send",
"name": "追加案内送信",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
7020,
170
],
"parameters": {
"method": "POST",
"url": "={{ $('JWTクレーム組み立て').item.json.apiUrl }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('アクセストークン取得').item.json.access_token }}"
},
{
"name": "Content-Type",
"value": "application/json;charset=UTF-8"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ content: $('追加案内メッセージ組み立て').item.json.messageContent }) }}",
"options": {}
}
},
{
"id": "exec-hc-sub-fallback",
"name": "Process実行(フォールバック)",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
5480,
350
],
"parameters": {
"workflowId": {
"__rl": true,
"mode": "id",
"value": "XRqcykbG2LuAjGG2"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"resultId": "={{ $('テキスト照合').item.json.resultId }}",
"processId": ""
}
}
}
},
{
"id": "code-date-parse",
"name": "日付パース",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
4820,
500
],
"parameters": {
"jsCode": "const ERA_INFO = {\n \"令和\": 2018,\n \"平成\": 1988,\n \"昭和\": 1925,\n \"大正\": 1911,\n};\n\nconst ERA_ALIASES = { R: \"令和\", H: \"平成\", S: \"昭和\", T: \"大正\" };\n\nfunction pad2(value) {\n return String(value).padStart(2, \"0\");\n}\n\nfunction finalizeDateParts(year, month, day) {\n if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) {\n throw new Error(\"日付形式で回答してください(例: 2026-03-01\");\n }\n const date = new Date(year, month - 1, day);\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n throw new Error(\"存在しない日付です\");\n }\n return `${year}-${pad2(month)}-${pad2(day)}`;\n}\n\nfunction tryParseEraDate(compact) {\n const match = compact.match(/^(令和|平成|昭和|大正|R|H|S|T)(.+)$/i);\n if (!match) return null;\n\n let era = match[1];\n if (/^[RHST]$/i.test(era)) {\n era = ERA_ALIASES[era.toUpperCase()] || era;\n }\n if (!ERA_INFO[era]) return null;\n\n const normalized = match[2]\n .replace(/年/g, \"-\")\n .replace(/月/g, \"-\")\n .replace(/日/g, \"\")\n .replace(/[.\\/]/g, \"-\");\n const parts = normalized.split(\"-\").filter((part) => part.length > 0);\n if (parts.length < 3) {\n throw new Error(\"月と日まで入力してください(例: 令和6年3月1日\");\n }\n\n const eraYear = Number(parts[0]);\n const month = Number(parts[1]);\n const day = Number(parts[2]);\n if (!Number.isFinite(eraYear) || !Number.isFinite(month) || !Number.isFinite(day)) {\n throw new Error(\"日付形式で回答してください(例: 令和6年3月1日\");\n }\n\n return finalizeDateParts(ERA_INFO[era] + eraYear, month, day);\n}\n\nfunction tryParseMonthDay(compact) {\n const match = compact.match(/^(\\d{1,2})(?:月|\\/|-|\\.)(\\d{1,2})(?:日)?$/);\n if (!match) return null;\n const currentYear = new Date().getFullYear();\n return finalizeDateParts(currentYear, Number(match[1]), Number(match[2]));\n}\n\nfunction parseDateInput(value) {\n const trimmed = String(value ?? \"\").trim();\n if (!trimmed) {\n throw new Error(\"日付が空です\");\n }\n const compact = trimmed.replace(/\\s+/g, \"\");\n\n const eraResult = tryParseEraDate(compact);\n if (eraResult) return eraResult;\n\n const monthDayResult = tryParseMonthDay(compact);\n if (monthDayResult) return monthDayResult;\n\n const normalized = compact\n .replace(/年/g, \"-\")\n .replace(/月/g, \"-\")\n .replace(/日/g, \"\")\n .replace(/[.\\/]/g, \"-\");\n const isoParts = normalized.split(\"-\").filter((part) => part.length > 0);\n if (isoParts.length === 3 && isoParts[0].length >= 4) {\n return finalizeDateParts(Number(isoParts[0]), Number(isoParts[1]), Number(isoParts[2]));\n }\n\n const parsed = new Date(trimmed);\n if (Number.isNaN(parsed.getTime())) {\n throw new Error(\"日付形式で回答してください(例: 2026-03-01 や 令和6年3月1日\");\n }\n return finalizeDateParts(parsed.getFullYear(), parsed.getMonth() + 1, parsed.getDate());\n}\n\nconst text = $('署名検証・送信者解決').item.json.text;\nconst stateItem = $('待機状態取得').item.json;\nlet result;\ntry {\n result = { success: true, parsedDate: parseDateInput(text) };\n} catch (err) {\n result = { success: false, errorMessage: err.message };\n}\n\nreturn [{\n json: {\n resultId: stateItem.resultId,\n awaitProcessId: stateItem.awaitProcessId,\n awaitColumn: stateItem.awaitColumn,\n ...result,\n },\n}];"
}
},
{
"id": "if-date-parse-ok",
"name": "日付パース成否判定",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
5040,
500
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "cond-1",
"leftValue": "={{ $('日付パース').item.json.success }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "http-date-column-update",
"name": "該当列update",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5260,
420
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('日付パース').item.json.resultId }}/update",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue, DateHash: { [$('日付パース').item.json.awaitColumn]: $('日付パース').item.json.parsedDate } }) }}",
"options": {}
}
},
{
"id": "dt-update-state-reset-date",
"name": "待機状態リセット(日付)",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
5480,
420
],
"parameters": {
"operation": "update",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "jqMDa2YZTI4f0iQ7"
},
"filters": {
"conditions": [
{
"keyName": "resultId",
"keyValue": "={{ String($('日付パース').item.json.resultId) }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"awaitInput": "none",
"awaitProcessId": "",
"awaitColumn": ""
},
"schema": [
{
"id": "awaitInput",
"displayName": "awaitInput",
"required": false,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "awaitProcessId",
"displayName": "awaitProcessId",
"required": false,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "awaitColumn",
"displayName": "awaitColumn",
"required": false,
"type": "string",
"canBeUsedToMatch": true
}
]
}
}
},
{
"id": "exec-hc-sub-date-done",
"name": "Process実行(日付完了)",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
5700,
420
],
"parameters": {
"workflowId": {
"__rl": true,
"mode": "id",
"value": "XRqcykbG2LuAjGG2"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"resultId": "={{ $('日付パース').item.json.resultId }}",
"processId": "={{ $('日付パース').item.json.awaitProcessId }}"
}
}
}
},
{
"id": "code-date-error-message",
"name": "日付エラーメッセージ組み立て",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5260,
600
],
"parameters": {
"jsCode": "const errorMessage = $('日付パース').item.json.errorMessage;\nreturn [{ json: { messageContent: { type: 'text', text: errorMessage } } }];"
}
},
{
"id": "http-date-error-send",
"name": "日付エラー送信",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
6600,
600
],
"parameters": {
"method": "POST",
"url": "={{ $('JWTクレーム組み立て(日付エラー)').item.json.apiUrl }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('アクセストークン取得(日付エラー)').item.json.access_token }}"
},
{
"name": "Content-Type",
"value": "application/json;charset=UTF-8"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ content: $('日付エラーメッセージ組み立て').item.json.messageContent }) }}",
"options": {}
}
},
{
"id": "if-content-is-file",
"name": "ファイル判定",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
4820,
800
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "cond-1",
"leftValue": "={{ $('署名検証・送信者解決').item.json.contentType === 'file' }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "http-lineworks-file-download",
"name": "LINEWORKSファイルダウンロード",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5920,
720
],
"parameters": {
"method": "GET",
"url": "={{ 'https://www.worksapis.com/v1.0/bots/' + $('JWTクレーム組み立て(ファイルDL)').item.json.botId + '/attachments/' + $('署名検証・送信者解決').item.json.fileId }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('アクセストークン取得(ファイルDL)').item.json.access_token }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
}
}
},
{
"id": "http-pleasanter-attachment-upload",
"name": "Pleasanter添付アップロード",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
6140,
720
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/binaries/upload",
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"parameterType": "formData",
"name": "ApiKey",
"value": "={{ $('PLEASANTER_API_KEY取得').item.json.configValue }}"
},
{
"parameterType": "formBinaryData",
"name": "file",
"inputDataFieldName": "data"
}
]
},
"options": {}
}
},
{
"id": "dt-update-state-reset-file",
"name": "待機状態リセット(ファイル)",
"type": "n8n-nodes-base.dataTable",
"typeVersion": 1,
"position": [
6360,
720
],
"parameters": {
"operation": "update",
"dataTableId": {
"__rl": true,
"mode": "id",
"value": "jqMDa2YZTI4f0iQ7"
},
"filters": {
"conditions": [
{
"keyName": "resultId",
"keyValue": "={{ String($('待機状態取得').item.json.resultId) }}"
}
]
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"awaitInput": "none",
"awaitProcessId": "",
"awaitColumn": ""
},
"schema": [
{
"id": "awaitInput",
"displayName": "awaitInput",
"required": false,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "awaitProcessId",
"displayName": "awaitProcessId",
"required": false,
"type": "string",
"canBeUsedToMatch": true
},
{
"id": "awaitColumn",
"displayName": "awaitColumn",
"required": false,
"type": "string",
"canBeUsedToMatch": true
}
]
}
}
},
{
"id": "exec-hc-sub-file-done",
"name": "Process実行(ファイル完了)",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
6580,
720
],
"parameters": {
"workflowId": {
"__rl": true,
"mode": "id",
"value": "XRqcykbG2LuAjGG2"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"resultId": "={{ $('待機状態取得').item.json.resultId }}",
"processId": "={{ $('待機状態取得').item.json.awaitProcessId }}"
}
}
}
},
{
"id": "code-file-missing-message",
"name": "ファイル未着メッセージ組み立て",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5040,
900
],
"parameters": {
"jsCode": "return [{ json: { messageContent: { type: 'text', text: 'ファイルを送信してください(テキストではなくファイルを添付して送信してください)' } } }];"
}
},
{
"id": "http-file-missing-send",
"name": "ファイル未着送信",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
6380,
950
],
"parameters": {
"method": "POST",
"url": "={{ $('JWTクレーム組み立て(ファイル未着)').item.json.apiUrl }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('アクセストークン取得(ファイル未着)').item.json.access_token }}"
},
{
"name": "Content-Type",
"value": "application/json;charset=UTF-8"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ content: $('ファイル未着メッセージ組み立て').item.json.messageContent }) }}",
"options": {}
}
},
{
"id": "respond-ok",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
8200,
500
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ result: \"ok\" }) }}",
"options": {}
}
},
{
"id": "http-bot-master-get-dateerr",
"name": "Botマスタ取得(日付エラー)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5480,
600
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('LINEWORKS_BOT_MASTER_SITE_ID取得').item.json.configValue }}/getsite",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue }) }}",
"options": {}
}
},
{
"id": "code-jwt-claims-dateerr",
"name": "JWTクレーム組み立て(日付エラー)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5700,
600
],
"parameters": {
"jsCode": "const now = Math.floor(Date.now() / 1000);\nconst jwtClaims = JSON.stringify({\n iss: $('LW_BOT_CLIENT_ID取得').item.json.configValue,\n sub: $('LW_BOT_SERVICE_ACCOUNT取得').item.json.configValue,\n iat: now,\n exp: now + 3600,\n aud: 'https://auth.worksmobile.com/oauth2/v2.0/token',\n});\n\n// --- Bot ID解決 ---\n// TODO(Task 12): 484184(LINEWORKS_BOT_MASTER_SITE_ID)の実データ構造は未調査。Task 9(HC-SUB)と同じ暫定実装を踏襲する。\nconst botMasterSiteSettings = $('Botマスタ取得(日付エラー)').item.json.Response.Data.SiteSettings;\nconst candidateBotIds = botMasterSiteSettings.BotIds\n || (botMasterSiteSettings.Processes || []).map((p) => p.BotId).filter(Boolean);\nconst botId = candidateBotIds && candidateBotIds[0];\nif (!botId) {\n throw new Error('BotIdを484184(LINEWORKS_BOT_MASTER_SITE_ID)から解決できませんでした。Task 12でBot選択ロジックを実装してください。');\n}\n\nconst targetEmail = $('署名検証・送信者解決').item.json.targetEmail;\nconst apiUrl = 'https://www.worksapis.com/v1.0/bots/' + botId + '/users/' + targetEmail + '/messages';\n\nreturn [{\n json: {\n jwtClaims,\n apiUrl,\n botId,\n targetEmail,\n },\n}];"
}
},
{
"id": "jwt-sign-dateerr",
"name": "Sign JWT(日付エラー)",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
5920,
600
],
"parameters": {
"operation": "sign",
"useJson": true,
"claimsJson": "={{ $json.jwtClaims }}",
"options": {
"algorithm": "RS256"
}
},
"credentials": {
"jwtAuth": {
"id": "Hw0qlEaGfLPnQWp1",
"name": "LINEWORKS Bot Private Key (v4)"
}
}
},
{
"id": "http-access-token-dateerr",
"name": "アクセストークン取得(日付エラー)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
6140,
600
],
"parameters": {
"method": "POST",
"url": "https://auth.worksmobile.com/oauth2/v2.0/token",
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "assertion",
"value": "={{ $json.token }}"
},
{
"name": "grant_type",
"value": "urn:ietf:params:oauth:grant-type:jwt-bearer"
},
{
"name": "client_id",
"value": "={{ $('LW_BOT_CLIENT_ID取得').item.json.configValue }}"
},
{
"name": "client_secret",
"value": "={{ $('LW_BOT_CLIENT_SECRET取得').item.json.configValue }}"
},
{
"name": "scope",
"value": "bot"
}
]
},
"options": {}
}
},
{
"id": "http-bot-master-get-filemiss",
"name": "Botマスタ取得(ファイル未着)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5260,
950
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('LINEWORKS_BOT_MASTER_SITE_ID取得').item.json.configValue }}/getsite",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue }) }}",
"options": {}
}
},
{
"id": "code-jwt-claims-filemiss",
"name": "JWTクレーム組み立て(ファイル未着)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5480,
950
],
"parameters": {
"jsCode": "const now = Math.floor(Date.now() / 1000);\nconst jwtClaims = JSON.stringify({\n iss: $('LW_BOT_CLIENT_ID取得').item.json.configValue,\n sub: $('LW_BOT_SERVICE_ACCOUNT取得').item.json.configValue,\n iat: now,\n exp: now + 3600,\n aud: 'https://auth.worksmobile.com/oauth2/v2.0/token',\n});\n\n// --- Bot ID解決 ---\n// TODO(Task 12): 484184(LINEWORKS_BOT_MASTER_SITE_ID)の実データ構造は未調査。Task 9(HC-SUB)と同じ暫定実装を踏襲する。\nconst botMasterSiteSettings = $('Botマスタ取得(ファイル未着)').item.json.Response.Data.SiteSettings;\nconst candidateBotIds = botMasterSiteSettings.BotIds\n || (botMasterSiteSettings.Processes || []).map((p) => p.BotId).filter(Boolean);\nconst botId = candidateBotIds && candidateBotIds[0];\nif (!botId) {\n throw new Error('BotIdを484184(LINEWORKS_BOT_MASTER_SITE_ID)から解決できませんでした。Task 12でBot選択ロジックを実装してください。');\n}\n\nconst targetEmail = $('署名検証・送信者解決').item.json.targetEmail;\nconst apiUrl = 'https://www.worksapis.com/v1.0/bots/' + botId + '/users/' + targetEmail + '/messages';\n\nreturn [{\n json: {\n jwtClaims,\n apiUrl,\n botId,\n targetEmail,\n },\n}];"
}
},
{
"id": "jwt-sign-filemiss",
"name": "Sign JWT(ファイル未着)",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
5700,
950
],
"parameters": {
"operation": "sign",
"useJson": true,
"claimsJson": "={{ $json.jwtClaims }}",
"options": {
"algorithm": "RS256"
}
},
"credentials": {
"jwtAuth": {
"id": "Hw0qlEaGfLPnQWp1",
"name": "LINEWORKS Bot Private Key (v4)"
}
}
},
{
"id": "http-access-token-filemiss",
"name": "アクセストークン取得(ファイル未着)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5920,
950
],
"parameters": {
"method": "POST",
"url": "https://auth.worksmobile.com/oauth2/v2.0/token",
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "assertion",
"value": "={{ $json.token }}"
},
{
"name": "grant_type",
"value": "urn:ietf:params:oauth:grant-type:jwt-bearer"
},
{
"name": "client_id",
"value": "={{ $('LW_BOT_CLIENT_ID取得').item.json.configValue }}"
},
{
"name": "client_secret",
"value": "={{ $('LW_BOT_CLIENT_SECRET取得').item.json.configValue }}"
},
{
"name": "scope",
"value": "bot"
}
]
},
"options": {}
}
},
{
"id": "http-bot-master-get-filedl",
"name": "Botマスタ取得(ファイルDL)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5040,
650
],
"parameters": {
"method": "POST",
"url": "={{ $('PLEASANTER_BASE_URL取得').item.json.configValue }}api/items/{{ $('LINEWORKS_BOT_MASTER_SITE_ID取得').item.json.configValue }}/getsite",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ ApiVersion: 1.1, ApiKey: $('PLEASANTER_API_KEY取得').item.json.configValue }) }}",
"options": {}
}
},
{
"id": "code-jwt-claims-filedl",
"name": "JWTクレーム組み立て(ファイルDL)",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5260,
650
],
"parameters": {
"jsCode": "const now = Math.floor(Date.now() / 1000);\nconst jwtClaims = JSON.stringify({\n iss: $('LW_BOT_CLIENT_ID取得').item.json.configValue,\n sub: $('LW_BOT_SERVICE_ACCOUNT取得').item.json.configValue,\n iat: now,\n exp: now + 3600,\n aud: 'https://auth.worksmobile.com/oauth2/v2.0/token',\n});\n\n// --- Bot ID解決 ---\n// TODO(Task 12): 484184(LINEWORKS_BOT_MASTER_SITE_ID)の実データ構造は未調査。Task 9(HC-SUB)と同じ暫定実装を踏襲する。\nconst botMasterSiteSettings = $('Botマスタ取得(ファイルDL)').item.json.Response.Data.SiteSettings;\nconst candidateBotIds = botMasterSiteSettings.BotIds\n || (botMasterSiteSettings.Processes || []).map((p) => p.BotId).filter(Boolean);\nconst botId = candidateBotIds && candidateBotIds[0];\nif (!botId) {\n throw new Error('BotIdを484184(LINEWORKS_BOT_MASTER_SITE_ID)から解決できませんでした。Task 12でBot選択ロジックを実装してください。');\n}\n\nconst targetEmail = $('署名検証・送信者解決').item.json.targetEmail;\nconst apiUrl = 'https://www.worksapis.com/v1.0/bots/' + botId + '/users/' + targetEmail + '/messages';\n\nreturn [{\n json: {\n jwtClaims,\n apiUrl,\n botId,\n targetEmail,\n },\n}];"
}
},
{
"id": "jwt-sign-filedl",
"name": "Sign JWT(ファイルDL)",
"type": "n8n-nodes-base.jwt",
"typeVersion": 1,
"position": [
5480,
650
],
"parameters": {
"operation": "sign",
"useJson": true,
"claimsJson": "={{ $json.jwtClaims }}",
"options": {
"algorithm": "RS256"
}
},
"credentials": {
"jwtAuth": {
"id": "Hw0qlEaGfLPnQWp1",
"name": "LINEWORKS Bot Private Key (v4)"
}
}
},
{
"id": "http-access-token-filedl",
"name": "アクセストークン取得(ファイルDL)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
5700,
650
],
"parameters": {
"method": "POST",
"url": "https://auth.worksmobile.com/oauth2/v2.0/token",
"sendBody": true,
"contentType": "form-urlencoded",
"bodyParameters": {
"parameters": [
{
"name": "assertion",
"value": "={{ $json.token }}"
},
{
"name": "grant_type",
"value": "urn:ietf:params:oauth:grant-type:jwt-bearer"
},
{
"name": "client_id",
"value": "={{ $('LW_BOT_CLIENT_ID取得').item.json.configValue }}"
},
{
"name": "client_secret",
"value": "={{ $('LW_BOT_CLIENT_SECRET取得').item.json.configValue }}"
},
{
"name": "scope",
"value": "bot"
}
]
},
"options": {}
}
},
{
"id": "if-state-exists",
"name": "待機状態存在チェック",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
2400,
680
],
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "loose",
"version": 2
},
"conditions": [
{
"id": "cond-1",
"leftValue": "={{ !!($('待機状態取得').item.json.awaitInput) }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
}
},
{
"id": "respond-error-no-state",
"name": "Respond to Webhook(待機状態エラー)",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
2620,
800
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ result: \"error\", message: \"待機状態が見つかりません\" }) }}",
"options": {}
}
},
{
"id": "respond-error-switch-fallback",
"name": "Respond to Webhook(分岐エラー)",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
4820,
1150
],
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ result: \"error\", message: \"未対応のawaitInput値です: \" + ($('待機状態取得').item.json.awaitInput ?? '(undefined)') }) }}",
"options": {}
}
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "LINEWORKS_BOT_SECRET取得",
"type": "main",
"index": 0
}
]
]
},
"LINEWORKS_BOT_SECRET取得": {
"main": [
[
{
"node": "署名検証・送信者解決",
"type": "main",
"index": 0
}
]
]
},
"署名検証・送信者解決": {
"main": [
[
{
"node": "PLEASANTER_BASE_URL取得",
"type": "main",
"index": 0
}
]
]
},
"PLEASANTER_BASE_URL取得": {
"main": [
[
{
"node": "PLEASANTER_API_KEY取得",
"type": "main",
"index": 0
}
]
]
},
"PLEASANTER_API_KEY取得": {
"main": [
[
{
"node": "HEALTHCHECK_SITE_ID取得",
"type": "main",
"index": 0
}
]
]
},
"HEALTHCHECK_SITE_ID取得": {
"main": [
[
{
"node": "対象者UserId解決",
"type": "main",
"index": 0
}
]
]
},
"対象者UserId解決": {
"main": [
[
{
"node": "対象者UserId特定",
"type": "main",
"index": 0
}
]
]
},
"対象者UserId特定": {
"main": [
[
{
"node": "対象レコード検索",
"type": "main",
"index": 0
}
]
]
},
"対象レコード検索": {
"main": [
[
{
"node": "対象レコード選定",
"type": "main",
"index": 0
}
]
]
},
"対象レコード選定": {
"main": [
[
{
"node": "待機状態取得",
"type": "main",
"index": 0
}
]
]
},
"待機状態取得": {
"main": [
[
{
"node": "待機状態存在チェック",
"type": "main",
"index": 0
}
]
]
},
"サイト設定取得": {
"main": [
[
{
"node": "LW_BOT_CLIENT_ID取得",
"type": "main",
"index": 0
}
]
]
},
"LW_BOT_CLIENT_ID取得": {
"main": [
[
{
"node": "LW_BOT_CLIENT_SECRET取得",
"type": "main",
"index": 0
}
]
]
},
"LW_BOT_CLIENT_SECRET取得": {
"main": [
[
{
"node": "LW_BOT_SERVICE_ACCOUNT取得",
"type": "main",
"index": 0
}
]
]
},
"LW_BOT_SERVICE_ACCOUNT取得": {
"main": [
[
{
"node": "LINEWORKS_BOT_MASTER_SITE_ID取得",
"type": "main",
"index": 0
}
]
]
},
"LINEWORKS_BOT_MASTER_SITE_ID取得": {
"main": [
[
{
"node": "awaitInput分岐",
"type": "main",
"index": 0
}
]
]
},
"awaitInput分岐": {
"main": [
[
{
"node": "テキスト照合",
"type": "main",
"index": 0
}
],
[
{
"node": "日付パース",
"type": "main",
"index": 0
}
],
[
{
"node": "ファイル判定",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook(分岐エラー)",
"type": "main",
"index": 0
}
]
]
},
"テキスト照合": {
"main": [
[
{
"node": "テキスト一致判定",
"type": "main",
"index": 0
}
]
]
},
"テキスト一致判定": {
"main": [
[
{
"node": "入力待ち種別判定",
"type": "main",
"index": 0
}
],
[
{
"node": "Process実行(フォールバック)",
"type": "main",
"index": 0
}
]
]
},
"入力待ち種別判定": {
"main": [
[
{
"node": "即時実行判定",
"type": "main",
"index": 0
}
]
]
},
"即時実行判定": {
"main": [
[
{
"node": "Process実行(即時)",
"type": "main",
"index": 0
}
],
[
{
"node": "待機状態保存(入力待ち)",
"type": "main",
"index": 0
}
]
]
},
"待機状態保存(入力待ち)": {
"main": [
[
{
"node": "追加案内メッセージ組み立て",
"type": "main",
"index": 0
}
]
]
},
"追加案内メッセージ組み立て": {
"main": [
[
{
"node": "Botマスタ取得",
"type": "main",
"index": 0
}
]
]
},
"Process実行(即時)": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"追加案内送信": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Process実行(フォールバック)": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"日付パース": {
"main": [
[
{
"node": "日付パース成否判定",
"type": "main",
"index": 0
}
]
]
},
"日付パース成否判定": {
"main": [
[
{
"node": "該当列update",
"type": "main",
"index": 0
}
],
[
{
"node": "日付エラーメッセージ組み立て",
"type": "main",
"index": 0
}
]
]
},
"該当列update": {
"main": [
[
{
"node": "待機状態リセット(日付)",
"type": "main",
"index": 0
}
]
]
},
"待機状態リセット(日付)": {
"main": [
[
{
"node": "Process実行(日付完了)",
"type": "main",
"index": 0
}
]
]
},
"日付エラーメッセージ組み立て": {
"main": [
[
{
"node": "Botマスタ取得(日付エラー)",
"type": "main",
"index": 0
}
]
]
},
"Process実行(日付完了)": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"日付エラー送信": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"ファイル判定": {
"main": [
[
{
"node": "Botマスタ取得(ファイルDL)",
"type": "main",
"index": 0
}
],
[
{
"node": "ファイル未着メッセージ組み立て",
"type": "main",
"index": 0
}
]
]
},
"LINEWORKSファイルダウンロード": {
"main": [
[
{
"node": "Pleasanter添付アップロード",
"type": "main",
"index": 0
}
]
]
},
"Pleasanter添付アップロード": {
"main": [
[
{
"node": "待機状態リセット(ファイル)",
"type": "main",
"index": 0
}
]
]
},
"待機状態リセット(ファイル)": {
"main": [
[
{
"node": "Process実行(ファイル完了)",
"type": "main",
"index": 0
}
]
]
},
"ファイル未着メッセージ組み立て": {
"main": [
[
{
"node": "Botマスタ取得(ファイル未着)",
"type": "main",
"index": 0
}
]
]
},
"Process実行(ファイル完了)": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"ファイル未着送信": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Botマスタ取得": {
"main": [
[
{
"node": "JWTクレーム組み立て",
"type": "main",
"index": 0
}
]
]
},
"JWTクレーム組み立て": {
"main": [
[
{
"node": "Sign JWT",
"type": "main",
"index": 0
}
]
]
},
"Sign JWT": {
"main": [
[
{
"node": "アクセストークン取得",
"type": "main",
"index": 0
}
]
]
},
"アクセストークン取得": {
"main": [
[
{
"node": "追加案内送信",
"type": "main",
"index": 0
}
]
]
},
"Botマスタ取得(日付エラー)": {
"main": [
[
{
"node": "JWTクレーム組み立て(日付エラー)",
"type": "main",
"index": 0
}
]
]
},
"JWTクレーム組み立て(日付エラー)": {
"main": [
[
{
"node": "Sign JWT(日付エラー)",
"type": "main",
"index": 0
}
]
]
},
"Sign JWT(日付エラー)": {
"main": [
[
{
"node": "アクセストークン取得(日付エラー)",
"type": "main",
"index": 0
}
]
]
},
"アクセストークン取得(日付エラー)": {
"main": [
[
{
"node": "日付エラー送信",
"type": "main",
"index": 0
}
]
]
},
"Botマスタ取得(ファイル未着)": {
"main": [
[
{
"node": "JWTクレーム組み立て(ファイル未着)",
"type": "main",
"index": 0
}
]
]
},
"JWTクレーム組み立て(ファイル未着)": {
"main": [
[
{
"node": "Sign JWT(ファイル未着)",
"type": "main",
"index": 0
}
]
]
},
"Sign JWT(ファイル未着)": {
"main": [
[
{
"node": "アクセストークン取得(ファイル未着)",
"type": "main",
"index": 0
}
]
]
},
"アクセストークン取得(ファイル未着)": {
"main": [
[
{
"node": "ファイル未着送信",
"type": "main",
"index": 0
}
]
]
},
"Botマスタ取得(ファイルDL)": {
"main": [
[
{
"node": "JWTクレーム組み立て(ファイルDL)",
"type": "main",
"index": 0
}
]
]
},
"JWTクレーム組み立て(ファイルDL)": {
"main": [
[
{
"node": "Sign JWT(ファイルDL)",
"type": "main",
"index": 0
}
]
]
},
"Sign JWT(ファイルDL)": {
"main": [
[
{
"node": "アクセストークン取得(ファイルDL)",
"type": "main",
"index": 0
}
]
]
},
"アクセストークン取得(ファイルDL)": {
"main": [
[
{
"node": "LINEWORKSファイルダウンロード",
"type": "main",
"index": 0
}
]
]
},
"待機状態存在チェック": {
"main": [
[
{
"node": "サイト設定取得",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond to Webhook(待機状態エラー)",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}