@openclaw/line 2026.7.2-beta.5 → 2026.7.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/.setup/accounts-DJVOv1JI.mjs +107 -0
- package/dist/.setup/api-D3h9w6oz.mjs +3 -0
- package/dist/.setup/api-DdlGHKoX.mjs +11 -0
- package/dist/.setup/basic-cards-C8Qwh2X1.mjs +307 -0
- package/dist/{card-command-BEMIm8D8.js → .setup/card-command-CWrkUiGY.mjs} +51 -23
- package/dist/{channel-CTaWrzfl.js → .setup/channel-Du0PrvOH.mjs} +76 -66
- package/dist/.setup/channel.runtime-b3suPB-W.mjs +4 -0
- package/dist/.setup/flex-templates-BP6VHjzK.mjs +814 -0
- package/dist/{markdown-to-line-Dn00k1bq.js → .setup/markdown-to-line-o89GT1Qn.mjs} +252 -254
- package/dist/{monitor-DzFU5UPV.js → .setup/monitor-DpxjN6Ks.mjs} +488 -686
- package/dist/.setup/monitor.runtime-BS-cGD5Q.mjs +2 -0
- package/dist/.setup/outbound.runtime-BeNDuEIF.mjs +2 -0
- package/dist/{probe-CM03wo6r.js → .setup/probe-BslD77tJ.mjs} +9 -7
- package/dist/.setup/probe.runtime-YwWmWIlk.mjs +2 -0
- package/dist/{reply-payload-transform-BRM2fRvK.js → .setup/reply-payload-transform-CYQIy_x8.mjs} +204 -80
- package/dist/{setup-surface-BHQOSD8R.js → .setup/setup-surface-M1ivmZ-G.mjs} +110 -143
- package/dist/api.js +2 -11
- package/dist/channel-plugin-api.js +1 -1
- package/dist/contract-api.js +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime-api.js +55 -13
- package/dist/setup-api.js +1 -1
- package/dist/setup-entry.js +2 -1
- package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupCreateRoute.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupFailedType.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupJobFailedType.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupJobStatus.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupStatus.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexBoxBorderWidth.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexBoxCornerRadius.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexBubble.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexIconSize.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexSpanSize.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexTextFontSize.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/numberOfMessagesResponse.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/postbackAction.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/subscriptionPeriodDemographic.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/version.d.ts +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/version.js +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/event.d.ts +1 -2
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/event.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/models.d.ts +0 -1
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/models.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/models.js +0 -1
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/models.js.map +1 -1
- package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupCreateRoute.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupFailedType.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupJobFailedType.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupJobStatus.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupStatus.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexBoxBorderWidth.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexBoxCornerRadius.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexBubble.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexIconSize.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexSpanSize.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexTextFontSize.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/numberOfMessagesResponse.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/postbackAction.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/messaging-api/model/subscriptionPeriodDemographic.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/version.d.ts +1 -1
- package/node_modules/@line/bot-sdk/dist/version.js +1 -1
- package/node_modules/@line/bot-sdk/dist/webhook/model/event.d.ts +1 -2
- package/node_modules/@line/bot-sdk/dist/webhook/model/event.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/webhook/model/models.d.ts +0 -1
- package/node_modules/@line/bot-sdk/dist/webhook/model/models.d.ts.map +1 -1
- package/node_modules/@line/bot-sdk/dist/webhook/model/models.js +0 -1
- package/node_modules/@line/bot-sdk/dist/webhook/model/models.js.map +1 -1
- package/node_modules/@line/bot-sdk/lib/insight/model/getNumberOfFollowersResponse.ts +1 -1
- package/node_modules/@line/bot-sdk/lib/liff/model/liffScope.ts +1 -1
- package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupCreateRoute.ts +4 -1
- package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupFailedType.ts +2 -1
- package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupJobFailedType.ts +2 -1
- package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupJobStatus.ts +4 -1
- package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupStatus.ts +6 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/botInfoResponse.ts +1 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexBoxBorderWidth.ts +6 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexBoxCornerRadius.ts +7 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexBubble.ts +7 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexIconSize.ts +10 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexSpanSize.ts +10 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexTextFontSize.ts +10 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/getAggregationUnitNameListResponse.ts +1 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/getFollowersResponse.ts +1 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/getJoinedMembershipUsersResponse.ts +1 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/issueLinkTokenResponse.ts +1 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/numberOfMessagesResponse.ts +5 -2
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/postbackAction.ts +4 -1
- package/node_modules/@line/bot-sdk/lib/messaging-api/model/subscriptionPeriodDemographic.ts +5 -1
- package/node_modules/@line/bot-sdk/lib/version.ts +1 -1
- package/node_modules/@line/bot-sdk/lib/webhook/.openapi-generator/FILES +0 -1
- package/node_modules/@line/bot-sdk/lib/webhook/model/event.ts +0 -2
- package/node_modules/@line/bot-sdk/lib/webhook/model/models.ts +0 -1
- package/node_modules/@line/bot-sdk/lib/webhook/model/stickerMessageContent.ts +1 -1
- package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/README.md +1 -1
- package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/fs/promises.d.ts +5 -16
- package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/package.json +3 -3
- package/node_modules/@line/bot-sdk/package.json +3 -3
- package/openclaw.plugin.json +75 -57
- package/package.json +6 -71
- package/dist/accounts-CSH5sk8U.js +0 -167
- package/dist/channel.runtime-D4Paci2y.js +0 -4
- package/dist/flex-templates-DVy_wog4.js +0 -1341
- package/dist/monitor.runtime-B8xjVqev.js +0 -2
- package/dist/outbound.runtime-eHl7NNHu.js +0 -2
- package/dist/probe.runtime-D13ZtXsd.js +0 -2
- package/dist/quick-reply-fallback-D86FwAfr.js +0 -56
- package/dist/send-receipt-CY2ey8XH.js +0 -107
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/messageEditedEvent.d.ts +0 -24
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/messageEditedEvent.d.ts.map +0 -1
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/messageEditedEvent.js +0 -14
- package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/messageEditedEvent.js.map +0 -1
- package/node_modules/@line/bot-sdk/dist/webhook/model/messageEditedEvent.d.ts +0 -24
- package/node_modules/@line/bot-sdk/dist/webhook/model/messageEditedEvent.d.ts.map +0 -1
- package/node_modules/@line/bot-sdk/dist/webhook/model/messageEditedEvent.js +0 -13
- package/node_modules/@line/bot-sdk/dist/webhook/model/messageEditedEvent.js.map +0 -1
- package/node_modules/@line/bot-sdk/lib/webhook/model/messageEditedEvent.ts +0 -32
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/LICENSE +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/assert/strict.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/assert.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/async_hooks.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/buffer.buffer.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/buffer.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/child_process.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/cluster.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/compatibility/iterators.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/console.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/constants.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/crypto.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dgram.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/diagnostics_channel.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dns/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dns.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/domain.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/events.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/fs.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/globals.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/globals.typedarray.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/http.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/http2.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/https.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/index.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/inspector.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/inspector.generated.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/module.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/net.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/os.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/path.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/perf_hooks.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/process.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/punycode.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/querystring.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/readline/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/readline.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/repl.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/sea.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/sqlite.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/consumers.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/web.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/string_decoder.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/test.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/timers/promises.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/timers.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/tls.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/trace_events.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/buffer.buffer.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/compatibility/float16array.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/globals.typedarray.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/index.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.7/compatibility/float16array.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.7/index.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/tty.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/url.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/util.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/v8.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/vm.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/wasi.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/abortcontroller.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/crypto.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/domexception.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/events.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/fetch.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/navigator.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/storage.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/streams.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/worker_threads.d.ts +0 -0
- /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/zlib.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/LICENSE +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/README.md +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/api.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/balanced-pool.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cache-interceptor.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cache.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/client-stats.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/client.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/connector.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/content-type.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cookies.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/diagnostics-channel.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/dispatcher.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/env-http-proxy-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/errors.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/eventsource.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/fetch.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/formdata.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/global-dispatcher.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/global-origin.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/h2c-client.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/handlers.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/header.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/index.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/interceptors.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-call-history.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-client.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-errors.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-interceptor.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-pool.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/package.json +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/patch.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/pool-stats.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/pool.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/proxy-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/readable.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/retry-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/retry-handler.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/round-robin-pool.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/snapshot-agent.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/util.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/utility.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/webidl.d.ts +0 -0
- /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/websocket.d.ts +0 -0
|
@@ -1,35 +1,30 @@
|
|
|
1
|
-
import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { E as buildTemplateMessageFromPayload, S as replyMessageLine, i as processLineMessage, l as createLocationMessage, p as getUserDisplayName, s as createFlexMessage, u as createQuickReplyItems, v as pushMessageLine, w as showLoadingAnimation, y as pushMessagesLine } from "./markdown-to-line-Dn00k1bq.js";
|
|
1
|
+
import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-DJVOv1JI.mjs";
|
|
2
|
+
import { f as resolveLineGroupConfigEntry, i as buildLineQuickReplyFallbackText, s as getLineRuntime } from "./reply-payload-transform-CYQIy_x8.mjs";
|
|
3
|
+
import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, E as replyMessageLine, O as showLoadingAnimation, S as pushMessageLine, T as pushTextMessageWithQuickReplies, _ as getUserDisplayName, c as processLineMessage, d as createFlexMessage, f as createImageMessage, h as createTextMessageWithQuickReplies, m as createQuickReplyItems, p as createLocationMessage } from "./markdown-to-line-o89GT1Qn.mjs";
|
|
5
4
|
import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
|
|
6
5
|
import { normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
|
-
import {
|
|
6
|
+
import { createMessageReceiveContext } from "openclaw/plugin-sdk/channel-outbound";
|
|
8
7
|
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
9
|
-
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
10
8
|
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
11
|
-
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
12
9
|
import { firstDefined } from "openclaw/plugin-sdk/allow-from";
|
|
13
|
-
import {
|
|
14
|
-
import { MediaFetchError } from "openclaw/plugin-sdk/media-runtime";
|
|
10
|
+
import { messagingApi } from "@line/bot-sdk";
|
|
15
11
|
import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
|
|
16
|
-
import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal
|
|
17
|
-
import { fetchWithRuntimeDispatcherOrMockedGlobal } from "openclaw/plugin-sdk/runtime-fetch";
|
|
18
|
-
import { HTTPFetchError } from "@line/bot-sdk";
|
|
12
|
+
import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal } from "openclaw/plugin-sdk/runtime-env";
|
|
19
13
|
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
20
|
-
import {
|
|
14
|
+
import { buildMentionRegexes, formatInboundEnvelope, formatInboundMediaUnavailableText, formatLocationText, hasFinalInboundReplyDispatch, matchesMentionPatterns, resolveInboundSessionEnvelopeContext, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
21
15
|
import { chunkMarkdownText } from "openclaw/plugin-sdk/reply-runtime";
|
|
22
16
|
import { isRequestBodyLimitError, normalizePluginHttpPath, normalizeWebhookPath, registerWebhookTargetWithPluginRoute, requestBodyErrorToText, resolveSingleWebhookTarget } from "openclaw/plugin-sdk/webhook-ingress";
|
|
23
|
-
import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1
|
|
17
|
+
import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1 } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
24
18
|
import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
25
19
|
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
26
20
|
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
27
|
-
import {
|
|
21
|
+
import { shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-auth-native";
|
|
28
22
|
import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, resolveConfiguredBindingRoute, resolvePairingIdLabel, resolvePinnedMainDmOwnerFromAllowlist, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
|
|
23
|
+
import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
29
24
|
import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
30
25
|
import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
26
|
+
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
|
31
27
|
import crypto from "node:crypto";
|
|
32
|
-
import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
|
|
33
28
|
//#region extensions/line/src/bot-access.ts
|
|
34
29
|
function normalizeLineAllowEntry(value) {
|
|
35
30
|
const trimmed = String(value).trim();
|
|
@@ -47,92 +42,9 @@ const normalizeAllowFrom = (list) => {
|
|
|
47
42
|
};
|
|
48
43
|
//#endregion
|
|
49
44
|
//#region extensions/line/src/download.ts
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const CONTENT_READY_TIMEOUT_MS = 15e3;
|
|
54
|
-
const LINE_CONTENT_BASE_URL = "https://api-data.line.me/v2/bot/message";
|
|
55
|
-
var RetryableLineMediaFetchError = class extends MediaFetchError {
|
|
56
|
-
constructor(message, options) {
|
|
57
|
-
super("fetch_failed", message, options);
|
|
58
|
-
this.name = "RetryableLineMediaFetchError";
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
function contentBackoffDelayMs(attempt) {
|
|
62
|
-
return Math.min(CONTENT_READY_BASE_DELAY_MS * 2 ** attempt, CONTENT_READY_MAX_DELAY_MS);
|
|
63
|
-
}
|
|
64
|
-
function lineContentUrl(messageId) {
|
|
65
|
-
return `${LINE_CONTENT_BASE_URL}/${encodeURIComponent(messageId)}/content`;
|
|
66
|
-
}
|
|
67
|
-
async function* lineResponseBodyChunks(response, messageId) {
|
|
68
|
-
const body = response.body;
|
|
69
|
-
if (!body) throw new RetryableLineMediaFetchError(`LINE media response for message ${messageId} had no body`);
|
|
70
|
-
const reader = body.getReader();
|
|
71
|
-
let completed = false;
|
|
72
|
-
try {
|
|
73
|
-
while (true) {
|
|
74
|
-
let chunk;
|
|
75
|
-
try {
|
|
76
|
-
chunk = await reader.read();
|
|
77
|
-
} catch (err) {
|
|
78
|
-
throw new RetryableLineMediaFetchError(`LINE media response stream failed for message ${messageId}`, { cause: err });
|
|
79
|
-
}
|
|
80
|
-
if (chunk.done) {
|
|
81
|
-
completed = true;
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (chunk.value.byteLength > 0) yield chunk.value;
|
|
85
|
-
}
|
|
86
|
-
} finally {
|
|
87
|
-
if (!completed) await reader.cancel().catch(() => void 0);
|
|
88
|
-
try {
|
|
89
|
-
reader.releaseLock();
|
|
90
|
-
} catch {}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
async function fetchLineContentWhenReady(messageId, channelAccessToken) {
|
|
94
|
-
const controller = new AbortController();
|
|
95
|
-
const deadline = setTimeout(() => controller.abort(), CONTENT_READY_TIMEOUT_MS);
|
|
96
|
-
deadline.unref();
|
|
97
|
-
try {
|
|
98
|
-
for (let attempt = 0; attempt < CONTENT_READY_MAX_ATTEMPTS; attempt++) {
|
|
99
|
-
const response = await fetchWithRuntimeDispatcherOrMockedGlobal(lineContentUrl(messageId), {
|
|
100
|
-
headers: { Authorization: `Bearer ${channelAccessToken}` },
|
|
101
|
-
redirect: "error",
|
|
102
|
-
signal: controller.signal
|
|
103
|
-
});
|
|
104
|
-
if (response.status === 200) {
|
|
105
|
-
if (!response.body) throw new RetryableLineMediaFetchError(`LINE media response for message ${messageId} had no body`);
|
|
106
|
-
return response;
|
|
107
|
-
}
|
|
108
|
-
await response.body?.cancel().catch(() => void 0);
|
|
109
|
-
if (response.status !== 202) throw new MediaFetchError("http_error", `LINE media download failed for message ${messageId} (HTTP ${response.status})`, { status: response.status });
|
|
110
|
-
if (attempt < CONTENT_READY_MAX_ATTEMPTS - 1) await setTimeout$1(contentBackoffDelayMs(attempt), void 0, { signal: controller.signal });
|
|
111
|
-
}
|
|
112
|
-
} catch (err) {
|
|
113
|
-
if (controller.signal.aborted) throw new RetryableLineMediaFetchError(`LINE media for message ${messageId} did not become ready within ${CONTENT_READY_TIMEOUT_MS / 1e3} seconds`, { cause: err });
|
|
114
|
-
if (err instanceof MediaFetchError) throw err;
|
|
115
|
-
throw new RetryableLineMediaFetchError(`LINE media download failed for message ${messageId}`, { cause: err });
|
|
116
|
-
} finally {
|
|
117
|
-
clearTimeout(deadline);
|
|
118
|
-
}
|
|
119
|
-
throw new MediaFetchError("http_error", `LINE media for message ${messageId} was still preparing (HTTP 202) after ${CONTENT_READY_MAX_ATTEMPTS} attempts`, { status: 202 });
|
|
120
|
-
}
|
|
121
|
-
function isRetryableLineInboundMediaError(err) {
|
|
122
|
-
if (err instanceof RetryableLineMediaFetchError) return true;
|
|
123
|
-
if (!(err instanceof MediaFetchError)) return false;
|
|
124
|
-
if (err.code === "http_error") return err.status === 202 || err.status === 408 || err.status === 429 || typeof err.status === "number" && err.status >= 500;
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10 * 1024 * 1024, options) {
|
|
128
|
-
const response = await fetchLineContentWhenReady(messageId, channelAccessToken);
|
|
129
|
-
let saved;
|
|
130
|
-
try {
|
|
131
|
-
saved = await saveMediaStream(lineResponseBodyChunks(response, messageId), response.headers.get("content-type") ?? void 0, "inbound", maxBytes, options?.originalFilename);
|
|
132
|
-
} catch (err) {
|
|
133
|
-
await response.body?.cancel().catch(() => void 0);
|
|
134
|
-
throw err;
|
|
135
|
-
}
|
|
45
|
+
async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10485760, options) {
|
|
46
|
+
const response = await new messagingApi.MessagingApiBlobClient({ channelAccessToken }).getMessageContent(messageId);
|
|
47
|
+
const saved = await saveMediaStream(response, void 0, "inbound", maxBytes, options?.originalFilename);
|
|
136
48
|
logVerbose(`line: persisted media ${messageId} to ${saved.path} (${saved.size} bytes)`);
|
|
137
49
|
return {
|
|
138
50
|
path: saved.path,
|
|
@@ -142,23 +54,15 @@ async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10 *
|
|
|
142
54
|
}
|
|
143
55
|
//#endregion
|
|
144
56
|
//#region extensions/line/src/auto-reply-delivery.ts
|
|
145
|
-
function toLineDeliveryError(error) {
|
|
146
|
-
return error instanceof Error ? error : new Error("LINE message send failed", { cause: error });
|
|
147
|
-
}
|
|
148
|
-
function getLineHttpError(error) {
|
|
149
|
-
if (error instanceof HTTPFetchError) return error;
|
|
150
|
-
return error instanceof Error && error.cause instanceof HTTPFetchError ? error.cause : void 0;
|
|
151
|
-
}
|
|
152
57
|
function markLineVisibleDeliveryError(error) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
Object.assign(deliveryError, {
|
|
58
|
+
if (error instanceof Error && Object.isExtensible(error)) {
|
|
59
|
+
Object.assign(error, {
|
|
156
60
|
sentBeforeError: true,
|
|
157
61
|
visibleReplySent: true
|
|
158
62
|
});
|
|
159
|
-
return
|
|
63
|
+
return error;
|
|
160
64
|
}
|
|
161
|
-
const visibleError = new Error("LINE message send failed", { cause:
|
|
65
|
+
const visibleError = new Error("LINE rich or media message send failed", { cause: error });
|
|
162
66
|
Object.assign(visibleError, {
|
|
163
67
|
sentBeforeError: true,
|
|
164
68
|
visibleReplySent: true
|
|
@@ -168,37 +72,12 @@ function markLineVisibleDeliveryError(error) {
|
|
|
168
72
|
async function deliverLineAutoReply(params) {
|
|
169
73
|
const { payload, lineData, replyToken, accountId, to, textLimit, deps } = params;
|
|
170
74
|
let replyTokenUsed = params.replyTokenUsed;
|
|
171
|
-
|
|
172
|
-
const sendVisible = async (send) => {
|
|
173
|
-
try {
|
|
174
|
-
const result = await send();
|
|
175
|
-
visibleReplySent = true;
|
|
176
|
-
return result;
|
|
177
|
-
} catch (error) {
|
|
178
|
-
if (visibleReplySent) throw markLineVisibleDeliveryError(error);
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
const replyVisible = (...args) => sendVisible(() => deps.replyMessageLine(...args));
|
|
183
|
-
const failedPushSegments = /* @__PURE__ */ new WeakMap();
|
|
184
|
-
const pushLineMessages = async (messages, allowFailedBatchTextRecovery, externalTail = []) => {
|
|
75
|
+
const pushLineMessages = async (messages) => {
|
|
185
76
|
if (messages.length === 0) return;
|
|
186
|
-
for (let i = 0; i < messages.length; i += 5) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
cfg: params.cfg,
|
|
191
|
-
accountId
|
|
192
|
-
}));
|
|
193
|
-
} catch (error) {
|
|
194
|
-
if (typeof error === "object" && error !== null) failedPushSegments.set(error, {
|
|
195
|
-
allowFailedBatchTextRecovery,
|
|
196
|
-
failedBatch: batch,
|
|
197
|
-
unattemptedTail: [...messages.slice(i + batch.length), ...externalTail]
|
|
198
|
-
});
|
|
199
|
-
throw error;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
77
|
+
for (let i = 0; i < messages.length; i += 5) await deps.pushMessagesLine(to, messages.slice(i, i + 5), {
|
|
78
|
+
cfg: params.cfg,
|
|
79
|
+
accountId
|
|
80
|
+
});
|
|
202
81
|
};
|
|
203
82
|
const sendLineMessages = async (messages, allowReplyToken) => {
|
|
204
83
|
if (messages.length === 0) return;
|
|
@@ -206,19 +85,18 @@ async function deliverLineAutoReply(params) {
|
|
|
206
85
|
if (allowReplyToken && replyToken && !replyTokenUsed) {
|
|
207
86
|
const replyBatch = remaining.slice(0, 5);
|
|
208
87
|
try {
|
|
209
|
-
await
|
|
88
|
+
await deps.replyMessageLine(replyToken, replyBatch, {
|
|
210
89
|
cfg: params.cfg,
|
|
211
90
|
accountId
|
|
212
91
|
});
|
|
213
92
|
} catch (err) {
|
|
214
93
|
deps.onReplyError?.(err);
|
|
215
|
-
await pushLineMessages(replyBatch
|
|
216
|
-
} finally {
|
|
217
|
-
replyTokenUsed = true;
|
|
94
|
+
await pushLineMessages(replyBatch);
|
|
218
95
|
}
|
|
96
|
+
replyTokenUsed = true;
|
|
219
97
|
remaining = remaining.slice(replyBatch.length);
|
|
220
98
|
}
|
|
221
|
-
if (remaining.length > 0) await pushLineMessages(remaining
|
|
99
|
+
if (remaining.length > 0) await pushLineMessages(remaining);
|
|
222
100
|
};
|
|
223
101
|
const richMessages = [];
|
|
224
102
|
const hasQuickReplies = Boolean(lineData.quickReplies?.length);
|
|
@@ -228,80 +106,80 @@ async function deliverLineAutoReply(params) {
|
|
|
228
106
|
if (templateMsg) richMessages.push(templateMsg);
|
|
229
107
|
}
|
|
230
108
|
if (lineData.location) richMessages.push(deps.createLocationMessage(lineData.location));
|
|
231
|
-
const
|
|
232
|
-
const processed = visibleText ? deps.processLineMessage(visibleText) : {
|
|
109
|
+
const processed = payload.text ? deps.processLineMessage(payload.text) : {
|
|
233
110
|
text: "",
|
|
234
111
|
flexMessages: []
|
|
235
112
|
};
|
|
236
113
|
for (const flexMsg of processed.flexMessages) richMessages.push(deps.createFlexMessage(truncateUtf16Safe(flexMsg.altText, 400), flexMsg.contents));
|
|
237
114
|
const chunks = processed.text ? deps.chunkMarkdownText(processed.text, textLimit) : [];
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const mediaMessages = [];
|
|
246
|
-
let deliveryError;
|
|
247
|
-
for (const rawUrl of mediaUrls) {
|
|
248
|
-
const url = rawUrl?.trim();
|
|
249
|
-
if (!url) continue;
|
|
250
|
-
try {
|
|
251
|
-
mediaMessages.push(await deps.buildMediaMessage(url, mediaOpts, to));
|
|
115
|
+
const mediaMessages = resolveSendableOutboundReplyParts(payload).mediaUrls.map((url) => url?.trim()).filter((url) => Boolean(url)).map((url) => deps.createImageMessage(url));
|
|
116
|
+
if (chunks.length > 0) {
|
|
117
|
+
const hasRichOrMedia = richMessages.length > 0 || mediaMessages.length > 0;
|
|
118
|
+
const sendRichBeforeText = hasQuickReplies && hasRichOrMedia;
|
|
119
|
+
let richMediaError;
|
|
120
|
+
if (sendRichBeforeText) try {
|
|
121
|
+
await sendLineMessages([...richMessages, ...mediaMessages], false);
|
|
252
122
|
} catch (err) {
|
|
253
|
-
|
|
123
|
+
richMediaError = err;
|
|
254
124
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
} catch (err) {
|
|
277
|
-
deliveryError ??= err;
|
|
278
|
-
const failedSegment = typeof err === "object" && err !== null ? failedPushSegments.get(err) : void 0;
|
|
279
|
-
const httpError = getLineHttpError(err);
|
|
280
|
-
const retryCandidates = failedSegment ? [...failedSegment.allowFailedBatchTextRecovery ? failedSegment.failedBatch : [], ...failedSegment.unattemptedTail] : [];
|
|
281
|
-
const retryTextMessages = retryCandidates.filter((message) => message.type === "text");
|
|
282
|
-
const quickRepliesNeedCarrier = hasQuickReplies && retryCandidates.some((message) => "quickReply" in message);
|
|
283
|
-
const retryMessages = retryTextMessages.length > 0 ? retryTextMessages : quickRepliesNeedCarrier ? [{
|
|
284
|
-
type: "text",
|
|
285
|
-
text: buildLineQuickReplyFallbackText(lineData.quickReplies),
|
|
286
|
-
quickReply: deps.createQuickReplyItems(lineData.quickReplies)
|
|
287
|
-
}] : [];
|
|
288
|
-
if (retryMessages.length > 0 && failedSegment?.failedBatch.some((message) => message.type !== "text") && httpError?.status === 400) try {
|
|
289
|
-
await sendLineMessages(retryMessages, false);
|
|
290
|
-
} catch {}
|
|
291
|
-
}
|
|
292
|
-
if (deliveryError !== void 0) {
|
|
293
|
-
if (!visibleReplySent) throw toLineDeliveryError(deliveryError);
|
|
294
|
-
return {
|
|
125
|
+
const { replyTokenUsed: nextReplyTokenUsed } = await deps.sendLineReplyChunks({
|
|
126
|
+
to,
|
|
127
|
+
chunks,
|
|
128
|
+
quickReplies: lineData.quickReplies,
|
|
129
|
+
replyToken,
|
|
130
|
+
replyTokenUsed,
|
|
131
|
+
cfg: params.cfg,
|
|
132
|
+
accountId,
|
|
133
|
+
replyMessageLine: deps.replyMessageLine,
|
|
134
|
+
pushMessageLine: deps.pushMessageLine,
|
|
135
|
+
pushTextMessageWithQuickReplies: deps.pushTextMessageWithQuickReplies,
|
|
136
|
+
createTextMessageWithQuickReplies: deps.createTextMessageWithQuickReplies
|
|
137
|
+
});
|
|
138
|
+
replyTokenUsed = nextReplyTokenUsed;
|
|
139
|
+
if (!sendRichBeforeText) try {
|
|
140
|
+
await sendLineMessages(richMessages, false);
|
|
141
|
+
if (mediaMessages.length > 0) await sendLineMessages(mediaMessages, false);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
richMediaError = err;
|
|
144
|
+
}
|
|
145
|
+
if (richMediaError !== void 0) return {
|
|
295
146
|
status: "partial",
|
|
296
147
|
replyTokenUsed,
|
|
297
|
-
|
|
298
|
-
error: markLineVisibleDeliveryError(deliveryError)
|
|
148
|
+
error: markLineVisibleDeliveryError(richMediaError)
|
|
299
149
|
};
|
|
150
|
+
} else {
|
|
151
|
+
const combined = [...richMessages, ...mediaMessages];
|
|
152
|
+
if (hasQuickReplies && combined.length === 0) {
|
|
153
|
+
const { replyTokenUsed: nextReplyTokenUsed } = await deps.sendLineReplyChunks({
|
|
154
|
+
to,
|
|
155
|
+
chunks: [buildLineQuickReplyFallbackText(lineData.quickReplies)],
|
|
156
|
+
quickReplies: lineData.quickReplies,
|
|
157
|
+
replyToken,
|
|
158
|
+
replyTokenUsed,
|
|
159
|
+
cfg: params.cfg,
|
|
160
|
+
accountId,
|
|
161
|
+
replyMessageLine: deps.replyMessageLine,
|
|
162
|
+
pushMessageLine: deps.pushMessageLine,
|
|
163
|
+
pushTextMessageWithQuickReplies: deps.pushTextMessageWithQuickReplies,
|
|
164
|
+
createTextMessageWithQuickReplies: deps.createTextMessageWithQuickReplies,
|
|
165
|
+
onReplyError: deps.onReplyError
|
|
166
|
+
});
|
|
167
|
+
replyTokenUsed = nextReplyTokenUsed;
|
|
168
|
+
} else {
|
|
169
|
+
if (hasQuickReplies && combined.length > 0) {
|
|
170
|
+
const quickReply = deps.createQuickReplyItems(lineData.quickReplies);
|
|
171
|
+
const targetIndex = replyToken && !replyTokenUsed ? Math.min(4, combined.length - 1) : combined.length - 1;
|
|
172
|
+
combined[targetIndex] = {
|
|
173
|
+
...combined[targetIndex],
|
|
174
|
+
quickReply
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
await sendLineMessages(combined, true);
|
|
178
|
+
}
|
|
300
179
|
}
|
|
301
180
|
return {
|
|
302
181
|
status: "delivered",
|
|
303
|
-
replyTokenUsed
|
|
304
|
-
visibleReplySent
|
|
182
|
+
replyTokenUsed
|
|
305
183
|
};
|
|
306
184
|
}
|
|
307
185
|
//#endregion
|
|
@@ -431,27 +309,48 @@ function extractMessageText(message) {
|
|
|
431
309
|
}
|
|
432
310
|
return "";
|
|
433
311
|
}
|
|
434
|
-
function
|
|
312
|
+
function extractMediaPlaceholder(message) {
|
|
435
313
|
switch (message.type) {
|
|
436
|
-
case "image": return "image";
|
|
437
|
-
case "video": return "video";
|
|
438
|
-
case "audio": return "audio";
|
|
439
|
-
case "file": return "document";
|
|
440
|
-
default: return;
|
|
314
|
+
case "image": return "<media:image>";
|
|
315
|
+
case "video": return "<media:video>";
|
|
316
|
+
case "audio": return "<media:audio>";
|
|
317
|
+
case "file": return "<media:document>";
|
|
318
|
+
default: return "";
|
|
441
319
|
}
|
|
442
320
|
}
|
|
321
|
+
function resolveLineConversationLabel(params) {
|
|
322
|
+
return params.isGroup ? params.groupId ? `group:${params.groupId}` : params.roomId ? `room:${params.roomId}` : "unknown-group" : params.senderLabel;
|
|
323
|
+
}
|
|
324
|
+
function resolveLineAddresses(params) {
|
|
325
|
+
const fromAddress = params.isGroup ? params.groupId ? `line:group:${params.groupId}` : params.roomId ? `line:room:${params.roomId}` : `line:${params.peerId}` : `line:${params.userId ?? params.peerId}`;
|
|
326
|
+
return {
|
|
327
|
+
fromAddress,
|
|
328
|
+
toAddress: params.isGroup ? fromAddress : `line:${params.userId ?? params.peerId}`,
|
|
329
|
+
originatingTo: params.isGroup ? fromAddress : `line:${params.userId ?? params.peerId}`
|
|
330
|
+
};
|
|
331
|
+
}
|
|
443
332
|
async function finalizeLineInboundContext(params) {
|
|
333
|
+
const { fromAddress, toAddress, originatingTo } = resolveLineAddresses({
|
|
334
|
+
isGroup: params.source.isGroup,
|
|
335
|
+
groupId: params.source.groupId,
|
|
336
|
+
roomId: params.source.roomId,
|
|
337
|
+
userId: params.source.userId,
|
|
338
|
+
peerId: params.source.peerId
|
|
339
|
+
});
|
|
444
340
|
const senderId = params.source.userId ?? "unknown";
|
|
445
341
|
const senderLabel = params.source.userId ? `user:${params.source.userId}` : "unknown";
|
|
446
|
-
const conversationLabel =
|
|
447
|
-
|
|
342
|
+
const conversationLabel = resolveLineConversationLabel({
|
|
343
|
+
isGroup: params.source.isGroup,
|
|
344
|
+
groupId: params.source.groupId,
|
|
345
|
+
roomId: params.source.roomId,
|
|
346
|
+
senderLabel
|
|
347
|
+
});
|
|
448
348
|
const { storePath, envelopeOptions, previousTimestamp } = resolveInboundSessionEnvelopeContext({
|
|
449
349
|
cfg: params.cfg,
|
|
450
350
|
agentId: params.route.agentId,
|
|
451
351
|
sessionKey: params.route.sessionKey
|
|
452
352
|
});
|
|
453
353
|
const agentBody = params.agentBody ?? params.rawBody;
|
|
454
|
-
const media = toInboundMediaFacts(params.media);
|
|
455
354
|
const body = formatInboundEnvelope({
|
|
456
355
|
channel: "LINE",
|
|
457
356
|
from: conversationLabel,
|
|
@@ -462,45 +361,38 @@ async function finalizeLineInboundContext(params) {
|
|
|
462
361
|
previousTimestamp,
|
|
463
362
|
envelope: envelopeOptions
|
|
464
363
|
});
|
|
465
|
-
const ctxPayload =
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
...params.locationContext,
|
|
498
|
-
GroupSubject: params.source.isGroup ? params.source.groupId ?? params.source.roomId : void 0,
|
|
499
|
-
GroupSystemPrompt: params.source.isGroup ? normalizeOptionalString(resolveLineGroupConfigEntry(params.account.config.groups, {
|
|
500
|
-
groupId: params.source.groupId,
|
|
501
|
-
roomId: params.source.roomId
|
|
502
|
-
})?.systemPrompt) : void 0
|
|
503
|
-
}
|
|
364
|
+
const ctxPayload = finalizeInboundContext({
|
|
365
|
+
Body: body,
|
|
366
|
+
BodyForAgent: agentBody,
|
|
367
|
+
RawBody: params.rawBody,
|
|
368
|
+
CommandBody: params.rawBody,
|
|
369
|
+
From: fromAddress,
|
|
370
|
+
To: toAddress,
|
|
371
|
+
SessionKey: params.route.sessionKey,
|
|
372
|
+
AccountId: params.route.accountId,
|
|
373
|
+
ChatType: params.source.isGroup ? "group" : "direct",
|
|
374
|
+
ConversationLabel: conversationLabel,
|
|
375
|
+
GroupSubject: params.source.isGroup ? params.source.groupId ?? params.source.roomId : void 0,
|
|
376
|
+
SenderId: senderId,
|
|
377
|
+
Provider: "line",
|
|
378
|
+
Surface: "line",
|
|
379
|
+
MessageSid: params.messageSid,
|
|
380
|
+
Timestamp: params.timestamp,
|
|
381
|
+
MediaPath: params.media.firstPath,
|
|
382
|
+
MediaType: params.media.firstContentType,
|
|
383
|
+
MediaUrl: params.media.firstPath,
|
|
384
|
+
MediaPaths: params.media.paths,
|
|
385
|
+
MediaUrls: params.media.paths,
|
|
386
|
+
MediaTypes: params.media.types,
|
|
387
|
+
...params.locationContext,
|
|
388
|
+
CommandAuthorized: params.commandAuthorized,
|
|
389
|
+
OriginatingChannel: "line",
|
|
390
|
+
OriginatingTo: originatingTo,
|
|
391
|
+
GroupSystemPrompt: params.source.isGroup ? normalizeOptionalString(resolveLineGroupConfigEntry(params.account.config.groups, {
|
|
392
|
+
groupId: params.source.groupId,
|
|
393
|
+
roomId: params.source.roomId
|
|
394
|
+
})?.systemPrompt) : void 0,
|
|
395
|
+
InboundHistory: params.inboundHistory
|
|
504
396
|
});
|
|
505
397
|
const pinnedMainDmOwner = !params.source.isGroup ? resolvePinnedMainDmOwnerFromAllowlist({
|
|
506
398
|
dmScope: params.cfg.session?.dmScope,
|
|
@@ -514,7 +406,8 @@ async function finalizeLineInboundContext(params) {
|
|
|
514
406
|
if (shouldLogVerbose()) {
|
|
515
407
|
const preview = truncateUtf16Safe(body, 200).replace(/\n/g, "\\n");
|
|
516
408
|
const mediaInfo = params.verboseLog.kind === "inbound" && (params.verboseLog.mediaCount ?? 0) > 1 ? ` mediaCount=${params.verboseLog.mediaCount}` : "";
|
|
517
|
-
|
|
409
|
+
const label = params.verboseLog.kind === "inbound" ? "line inbound" : "line postback";
|
|
410
|
+
logVerbose(`${label}: from=${ctxPayload.From} len=${body.length}${mediaInfo} preview="${preview}"`);
|
|
518
411
|
}
|
|
519
412
|
return {
|
|
520
413
|
ctxPayload,
|
|
@@ -543,7 +436,7 @@ async function finalizeLineInboundContext(params) {
|
|
|
543
436
|
};
|
|
544
437
|
}
|
|
545
438
|
async function buildLineMessageContext(params) {
|
|
546
|
-
const { event, allMedia, mediaUnavailable, cfg, account, commandAuthorized,
|
|
439
|
+
const { event, allMedia, mediaUnavailable, cfg, account, commandAuthorized, groupHistories, historyLimit } = params;
|
|
547
440
|
const source = event.source;
|
|
548
441
|
const { userId, groupId, roomId, isGroup, peerId, route } = await resolveLineInboundRoute({
|
|
549
442
|
source,
|
|
@@ -554,17 +447,15 @@ async function buildLineMessageContext(params) {
|
|
|
554
447
|
const messageId = message.id;
|
|
555
448
|
const timestamp = event.timestamp;
|
|
556
449
|
const textContent = extractMessageText(message);
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
kind: nativeMediaKind
|
|
561
|
-
})) : nativeMediaKind ? [{ kind: nativeMediaKind }] : [];
|
|
562
|
-
const rawBody = textContent;
|
|
450
|
+
const placeholder = extractMediaPlaceholder(message);
|
|
451
|
+
let rawBody = textContent || placeholder;
|
|
452
|
+
if (!rawBody && allMedia.length > 0) rawBody = `<media:image>${allMedia.length > 1 ? ` (${allMedia.length} images)` : ""}`;
|
|
563
453
|
const agentBody = mediaUnavailable ? formatInboundMediaUnavailableText({
|
|
564
454
|
body: rawBody,
|
|
455
|
+
mediaPlaceholder: placeholder,
|
|
565
456
|
notice: "[line attachment unavailable]"
|
|
566
457
|
}) : rawBody;
|
|
567
|
-
if (!agentBody &&
|
|
458
|
+
if (!agentBody && allMedia.length === 0) return null;
|
|
568
459
|
let locationContext;
|
|
569
460
|
if (message.type === "location") {
|
|
570
461
|
const loc = message;
|
|
@@ -575,6 +466,11 @@ async function buildLineMessageContext(params) {
|
|
|
575
466
|
address: loc.address
|
|
576
467
|
});
|
|
577
468
|
}
|
|
469
|
+
const historyKey = isGroup ? peerId : void 0;
|
|
470
|
+
const inboundHistory = historyKey && groupHistories && (historyLimit ?? 0) > 0 ? createChannelHistoryWindow({ historyMap: groupHistories }).buildInboundHistory({
|
|
471
|
+
historyKey,
|
|
472
|
+
limit: historyLimit ?? 0
|
|
473
|
+
}) : void 0;
|
|
578
474
|
const finalized = await finalizeLineInboundContext({
|
|
579
475
|
cfg,
|
|
580
476
|
account,
|
|
@@ -592,7 +488,12 @@ async function buildLineMessageContext(params) {
|
|
|
592
488
|
timestamp,
|
|
593
489
|
messageSid: messageId,
|
|
594
490
|
commandAuthorized,
|
|
595
|
-
media:
|
|
491
|
+
media: {
|
|
492
|
+
firstPath: allMedia[0]?.path,
|
|
493
|
+
firstContentType: allMedia[0]?.contentType,
|
|
494
|
+
paths: allMedia.length > 0 ? allMedia.map((m) => m.path) : void 0,
|
|
495
|
+
types: allMedia.length > 0 ? allMedia.map((m) => m.contentType).filter(Boolean) : void 0
|
|
496
|
+
},
|
|
596
497
|
locationContext,
|
|
597
498
|
verboseLog: {
|
|
598
499
|
kind: "inbound",
|
|
@@ -622,14 +523,18 @@ async function buildLinePostbackContext(params) {
|
|
|
622
523
|
account
|
|
623
524
|
});
|
|
624
525
|
const timestamp = event.timestamp;
|
|
625
|
-
const
|
|
626
|
-
if (!
|
|
627
|
-
let
|
|
628
|
-
if (
|
|
629
|
-
const searchParams = new URLSearchParams(
|
|
526
|
+
const rawBody = event.postback?.data?.trim() ?? "";
|
|
527
|
+
if (!rawBody) return null;
|
|
528
|
+
let agentBody = rawBody;
|
|
529
|
+
if (rawBody.includes("line.action=")) {
|
|
530
|
+
const searchParams = new URLSearchParams(rawBody);
|
|
630
531
|
const action = searchParams.get("line.action") ?? "";
|
|
631
532
|
const device = searchParams.get("line.device");
|
|
632
|
-
|
|
533
|
+
agentBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
|
|
534
|
+
}
|
|
535
|
+
for (const [key, value] of Object.entries(event.postback.params ?? {}).toSorted(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
|
|
536
|
+
const picked = normalizeOptionalString(value);
|
|
537
|
+
if (picked) agentBody += ` ${key}=${picked}`;
|
|
633
538
|
}
|
|
634
539
|
const messageSid = event.replyToken ? `postback:${event.replyToken}` : `postback:${timestamp}`;
|
|
635
540
|
const finalized = await finalizeLineInboundContext({
|
|
@@ -645,10 +550,16 @@ async function buildLinePostbackContext(params) {
|
|
|
645
550
|
peerId
|
|
646
551
|
},
|
|
647
552
|
rawBody,
|
|
553
|
+
agentBody,
|
|
648
554
|
timestamp,
|
|
649
555
|
messageSid,
|
|
650
556
|
commandAuthorized,
|
|
651
|
-
media:
|
|
557
|
+
media: {
|
|
558
|
+
firstPath: "",
|
|
559
|
+
firstContentType: void 0,
|
|
560
|
+
paths: void 0,
|
|
561
|
+
types: void 0
|
|
562
|
+
},
|
|
652
563
|
verboseLog: { kind: "postback" }
|
|
653
564
|
});
|
|
654
565
|
return {
|
|
@@ -665,41 +576,6 @@ async function buildLinePostbackContext(params) {
|
|
|
665
576
|
};
|
|
666
577
|
}
|
|
667
578
|
//#endregion
|
|
668
|
-
//#region extensions/line/src/group-history.ts
|
|
669
|
-
const reservedEntries = /* @__PURE__ */ new WeakSet();
|
|
670
|
-
function reserveLineGroupHistory(historyMap, historyKey, limit) {
|
|
671
|
-
if (!historyMap || !historyKey || limit <= 0) return {
|
|
672
|
-
commit: () => {},
|
|
673
|
-
release: () => {}
|
|
674
|
-
};
|
|
675
|
-
const consumedEntries = (historyMap.get(historyKey) ?? []).filter((entry) => !reservedEntries.has(entry));
|
|
676
|
-
for (const entry of consumedEntries) reservedEntries.add(entry);
|
|
677
|
-
const inboundHistory = createChannelHistoryWindow({ historyMap: /* @__PURE__ */ new Map([[historyKey, consumedEntries]]) }).buildInboundHistory({
|
|
678
|
-
historyKey,
|
|
679
|
-
limit
|
|
680
|
-
});
|
|
681
|
-
let settled = false;
|
|
682
|
-
const settle = () => {
|
|
683
|
-
if (settled) return false;
|
|
684
|
-
settled = true;
|
|
685
|
-
for (const entry of consumedEntries) reservedEntries.delete(entry);
|
|
686
|
-
return true;
|
|
687
|
-
};
|
|
688
|
-
return {
|
|
689
|
-
inboundHistory,
|
|
690
|
-
commit: () => {
|
|
691
|
-
if (!settle() || consumedEntries.length === 0) return;
|
|
692
|
-
const consumed = new Set(consumedEntries);
|
|
693
|
-
const kept = (historyMap.get(historyKey) ?? []).filter((entry) => !consumed.has(entry));
|
|
694
|
-
if (kept.length > 0) historyMap.set(historyKey, kept);
|
|
695
|
-
else historyMap.delete(historyKey);
|
|
696
|
-
},
|
|
697
|
-
release: () => {
|
|
698
|
-
settle();
|
|
699
|
-
}
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
//#endregion
|
|
703
579
|
//#region extensions/line/src/bot-handlers.ts
|
|
704
580
|
const LINE_DOWNLOADABLE_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
705
581
|
"image",
|
|
@@ -710,9 +586,63 @@ const LINE_DOWNLOADABLE_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
710
586
|
function isDownloadableLineMessageType(messageType) {
|
|
711
587
|
return LINE_DOWNLOADABLE_MESSAGE_TYPES.has(messageType);
|
|
712
588
|
}
|
|
589
|
+
const LINE_WEBHOOK_REPLAY_WINDOW_MS = 6e5;
|
|
590
|
+
const LINE_WEBHOOK_REPLAY_MAX_ENTRIES = 4096;
|
|
713
591
|
function normalizeLineIngressEntry(value) {
|
|
714
592
|
return normalizeLineAllowEntry(value) || null;
|
|
715
593
|
}
|
|
594
|
+
var LineRetryableWebhookError = class extends Error {
|
|
595
|
+
constructor(message, options) {
|
|
596
|
+
super(message, options);
|
|
597
|
+
this.name = "LineRetryableWebhookError";
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
function createLineWebhookReplayCache() {
|
|
601
|
+
return createClaimableDedupe({
|
|
602
|
+
ttlMs: LINE_WEBHOOK_REPLAY_WINDOW_MS,
|
|
603
|
+
memoryMaxSize: LINE_WEBHOOK_REPLAY_MAX_ENTRIES
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
function buildLineWebhookReplayKey(event, accountId) {
|
|
607
|
+
if (event.type === "message") {
|
|
608
|
+
const messageId = event.message?.id?.trim();
|
|
609
|
+
if (messageId) return {
|
|
610
|
+
key: `${accountId}|message:${messageId}`,
|
|
611
|
+
eventId: `message:${messageId}`
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
const eventId = event.webhookEventId?.trim();
|
|
615
|
+
if (!eventId) return null;
|
|
616
|
+
const source = event.source;
|
|
617
|
+
const sourceId = source?.type === "group" ? `group:${source.groupId ?? ""}` : source?.type === "room" ? `room:${source.roomId ?? ""}` : `user:${source?.userId ?? ""}`;
|
|
618
|
+
return {
|
|
619
|
+
key: `${accountId}|${event.type}|${sourceId}|${eventId}`,
|
|
620
|
+
eventId: `event:${eventId}`
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function getLineReplayCandidate(event, context) {
|
|
624
|
+
const replay = buildLineWebhookReplayKey(event, context.account.accountId);
|
|
625
|
+
const cache = context.replayCache;
|
|
626
|
+
if (!replay || !cache) return null;
|
|
627
|
+
return {
|
|
628
|
+
key: replay.key,
|
|
629
|
+
eventId: replay.eventId,
|
|
630
|
+
cache
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
async function claimLineReplayEvent(candidate) {
|
|
634
|
+
const claim = await candidate.cache.claim(candidate.key);
|
|
635
|
+
if (claim.kind === "claimed") return { skip: false };
|
|
636
|
+
if (claim.kind === "inflight") {
|
|
637
|
+
logVerbose(`line: skipped in-flight replayed webhook event ${candidate.eventId}`);
|
|
638
|
+
return {
|
|
639
|
+
skip: true,
|
|
640
|
+
inFlightResult: claim.pending.then(() => void 0)
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
logVerbose(`line: skipped replayed webhook event ${candidate.eventId}`);
|
|
644
|
+
return { skip: true };
|
|
645
|
+
}
|
|
716
646
|
function resolveLineGroupConfig(params) {
|
|
717
647
|
return resolveLineGroupConfigEntry(params.config.groups, {
|
|
718
648
|
groupId: params.groupId,
|
|
@@ -787,7 +717,7 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
787
717
|
defaultGroupPolicy: resolveDefaultGroupPolicy(cfg)
|
|
788
718
|
});
|
|
789
719
|
const groupPolicy = runtimeGroupPolicy === "disabled" ? "disabled" : groupConfig?.allowFrom !== void 0 ? "allowlist" : runtimeGroupPolicy;
|
|
790
|
-
const groupAllowFrom = normalizeStringEntries(firstDefined(groupConfig?.allowFrom, account.config.groupAllowFrom));
|
|
720
|
+
const groupAllowFrom = normalizeStringEntries(firstDefined(groupConfig?.allowFrom, account.config.groupAllowFrom, account.config.allowFrom?.length ? account.config.allowFrom : void 0));
|
|
791
721
|
const mentionFacts = (() => {
|
|
792
722
|
if (!isGroup || event.type !== "message") return {
|
|
793
723
|
canDetectMention: false,
|
|
@@ -852,7 +782,7 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
852
782
|
allowFrom: normalizeStringEntries(account.config.allowFrom),
|
|
853
783
|
groupAllowFrom,
|
|
854
784
|
command: {
|
|
855
|
-
hasControlCommand:
|
|
785
|
+
hasControlCommand: shouldComputeCommandAuthorized(rawText, cfg),
|
|
856
786
|
groupOwnerAllowFrom: "none"
|
|
857
787
|
}
|
|
858
788
|
});
|
|
@@ -950,42 +880,42 @@ async function handleMessageEvent(event, context) {
|
|
|
950
880
|
});
|
|
951
881
|
return;
|
|
952
882
|
}
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
try {
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
883
|
+
const allMedia = [];
|
|
884
|
+
let mediaUnavailable = false;
|
|
885
|
+
if (isDownloadableLineMessageType(message.type)) try {
|
|
886
|
+
const originalFilename = message.type === "file" ? normalizeOptionalString(message.fileName) : void 0;
|
|
887
|
+
const media = await downloadLineMedia(message.id, account.channelAccessToken, mediaMaxBytes, { originalFilename });
|
|
888
|
+
allMedia.push({
|
|
889
|
+
path: media.path,
|
|
890
|
+
contentType: media.contentType
|
|
891
|
+
});
|
|
892
|
+
} catch (err) {
|
|
893
|
+
mediaUnavailable = true;
|
|
894
|
+
const errMsg = String(err);
|
|
895
|
+
if (errMsg.includes("exceeds") && errMsg.includes("limit")) logVerbose(`line: media exceeds size limit for message ${message.id}`);
|
|
896
|
+
else runtime.error?.(danger(`line: failed to download media: ${errMsg}`));
|
|
897
|
+
}
|
|
898
|
+
const messageContext = await buildLineMessageContext({
|
|
899
|
+
event,
|
|
900
|
+
allMedia,
|
|
901
|
+
mediaUnavailable,
|
|
902
|
+
cfg,
|
|
903
|
+
account,
|
|
904
|
+
commandAuthorized: decision.commandAccess.authorized,
|
|
905
|
+
groupHistories: context.groupHistories,
|
|
906
|
+
historyLimit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
|
|
907
|
+
});
|
|
908
|
+
if (!messageContext) {
|
|
909
|
+
logVerbose("line: skipping empty message");
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
await processMessage(messageContext);
|
|
913
|
+
if (isGroup && context.groupHistories) {
|
|
914
|
+
const historyKey = groupId ?? roomId;
|
|
915
|
+
if (historyKey && context.groupHistories.has(historyKey)) createChannelHistoryWindow({ historyMap: context.groupHistories }).clear({
|
|
916
|
+
historyKey,
|
|
917
|
+
limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
|
|
980
918
|
});
|
|
981
|
-
if (!messageContext) {
|
|
982
|
-
logVerbose("line: skipping empty message");
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
await processMessage(messageContext, context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {});
|
|
986
|
-
historyReservation.commit();
|
|
987
|
-
} finally {
|
|
988
|
-
historyReservation.release();
|
|
989
919
|
}
|
|
990
920
|
}
|
|
991
921
|
async function handleFollowEvent(event, _context) {
|
|
@@ -1016,41 +946,56 @@ async function handlePostbackEvent(event, context) {
|
|
|
1016
946
|
commandAuthorized: decision.commandAccess.authorized
|
|
1017
947
|
});
|
|
1018
948
|
if (!postbackContext) return;
|
|
1019
|
-
await context.processMessage(postbackContext
|
|
949
|
+
await context.processMessage(postbackContext);
|
|
1020
950
|
}
|
|
1021
951
|
async function handleLineWebhookEvents(events, context) {
|
|
1022
952
|
let firstError;
|
|
1023
|
-
for (const event of events)
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
953
|
+
for (const event of events) {
|
|
954
|
+
const replayCandidate = getLineReplayCandidate(event, context);
|
|
955
|
+
const replaySkip = replayCandidate ? await claimLineReplayEvent(replayCandidate) : null;
|
|
956
|
+
if (replaySkip?.skip) {
|
|
957
|
+
if (replaySkip.inFlightResult) try {
|
|
958
|
+
await replaySkip.inFlightResult;
|
|
959
|
+
} catch (err) {
|
|
960
|
+
context.runtime.error?.(danger(`line: replayed in-flight event failed: ${String(err)}`));
|
|
961
|
+
firstError ??= err;
|
|
962
|
+
}
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
switch (event.type) {
|
|
967
|
+
case "message":
|
|
968
|
+
await handleMessageEvent(event, context);
|
|
969
|
+
break;
|
|
970
|
+
case "follow":
|
|
971
|
+
await handleFollowEvent(event, context);
|
|
972
|
+
break;
|
|
973
|
+
case "unfollow":
|
|
974
|
+
await handleUnfollowEvent(event, context);
|
|
975
|
+
break;
|
|
976
|
+
case "join":
|
|
977
|
+
await handleJoinEvent(event, context);
|
|
978
|
+
break;
|
|
979
|
+
case "leave":
|
|
980
|
+
await handleLeaveEvent(event, context);
|
|
981
|
+
break;
|
|
982
|
+
case "postback":
|
|
983
|
+
await handlePostbackEvent(event, context);
|
|
984
|
+
break;
|
|
985
|
+
default: logVerbose(`line: unhandled event type: ${event.type}`);
|
|
986
|
+
}
|
|
987
|
+
if (replayCandidate) await replayCandidate.cache.commit(replayCandidate.key);
|
|
988
|
+
} catch (err) {
|
|
989
|
+
if (replayCandidate) {
|
|
990
|
+
if (err instanceof LineRetryableWebhookError) replayCandidate.cache.release(replayCandidate.key, { error: err });
|
|
991
|
+
else await replayCandidate.cache.commit(replayCandidate.key);
|
|
992
|
+
}
|
|
993
|
+
context.runtime.error?.(danger(`line: event handler failed: ${String(err)}`));
|
|
994
|
+
firstError ??= err;
|
|
995
|
+
}
|
|
1028
996
|
}
|
|
1029
997
|
if (firstError) throw toLintErrorObject(firstError, "Non-Error thrown");
|
|
1030
998
|
}
|
|
1031
|
-
async function handleLineWebhookEvent(event, context) {
|
|
1032
|
-
switch (event.type) {
|
|
1033
|
-
case "message":
|
|
1034
|
-
await handleMessageEvent(event, context);
|
|
1035
|
-
break;
|
|
1036
|
-
case "follow":
|
|
1037
|
-
await handleFollowEvent(event, context);
|
|
1038
|
-
break;
|
|
1039
|
-
case "unfollow":
|
|
1040
|
-
await handleUnfollowEvent(event, context);
|
|
1041
|
-
break;
|
|
1042
|
-
case "join":
|
|
1043
|
-
await handleJoinEvent(event, context);
|
|
1044
|
-
break;
|
|
1045
|
-
case "leave":
|
|
1046
|
-
await handleLeaveEvent(event, context);
|
|
1047
|
-
break;
|
|
1048
|
-
case "postback":
|
|
1049
|
-
await handlePostbackEvent(event, context);
|
|
1050
|
-
break;
|
|
1051
|
-
default: logVerbose(`line: unhandled event type: ${event.type}`);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
999
|
function toLintErrorObject(value, fallbackMessage) {
|
|
1055
1000
|
if (value instanceof Error) return value;
|
|
1056
1001
|
if (typeof value === "string") return new Error(value);
|
|
@@ -1059,257 +1004,6 @@ function toLintErrorObject(value, fallbackMessage) {
|
|
|
1059
1004
|
return error;
|
|
1060
1005
|
}
|
|
1061
1006
|
//#endregion
|
|
1062
|
-
//#region extensions/line/src/webhook-spool.ts
|
|
1063
|
-
const LINE_WEBHOOK_SPOOL_VERSION = 1;
|
|
1064
|
-
const LINE_WEBHOOK_DRAIN_INTERVAL_MS = 500;
|
|
1065
|
-
const LINE_WEBHOOK_MAX_CONCURRENT_DELIVERIES = 8;
|
|
1066
|
-
const LINE_WEBHOOK_DRAIN_SCAN_LIMIT = 100;
|
|
1067
|
-
const LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS = 5e3;
|
|
1068
|
-
const LINE_WEBHOOK_TOMBSTONE_TTL_MS = 720 * 60 * 6e4;
|
|
1069
|
-
const LINE_WEBHOOK_TOMBSTONE_MAX_ENTRIES = 4096;
|
|
1070
|
-
var LineWebhookPayloadError = class extends Error {
|
|
1071
|
-
constructor(message, options) {
|
|
1072
|
-
super(message, options);
|
|
1073
|
-
this.name = "LineWebhookPayloadError";
|
|
1074
|
-
}
|
|
1075
|
-
};
|
|
1076
|
-
var LineWebhookTerminalDeliveryError = class extends Error {
|
|
1077
|
-
constructor(message, options) {
|
|
1078
|
-
super(message, options);
|
|
1079
|
-
this.reason = "delivery-side-effects-committed";
|
|
1080
|
-
this.name = "LineWebhookTerminalDeliveryError";
|
|
1081
|
-
}
|
|
1082
|
-
};
|
|
1083
|
-
function nonEmptyString(value) {
|
|
1084
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1085
|
-
}
|
|
1086
|
-
/** Message ids preserve the shipped replay-guard keyspace; other events use LINE's delivery id. */
|
|
1087
|
-
function eventIdFor(event) {
|
|
1088
|
-
if (!event || typeof event !== "object") throw new LineWebhookPayloadError("LINE webhook event must be an object.");
|
|
1089
|
-
const candidate = event;
|
|
1090
|
-
if (candidate.type === "message") {
|
|
1091
|
-
const messageId = nonEmptyString(candidate.message?.id);
|
|
1092
|
-
if (messageId) return `message:${messageId}`;
|
|
1093
|
-
}
|
|
1094
|
-
const webhookEventId = nonEmptyString(candidate.webhookEventId);
|
|
1095
|
-
if (webhookEventId) return `event:${webhookEventId}`;
|
|
1096
|
-
throw new LineWebhookPayloadError("LINE webhook event is missing a stable delivery id.");
|
|
1097
|
-
}
|
|
1098
|
-
function laneKeyFor(event, eventId) {
|
|
1099
|
-
if (!event || typeof event !== "object") return eventId;
|
|
1100
|
-
const source = event.source;
|
|
1101
|
-
if (source?.type === "group") {
|
|
1102
|
-
const groupId = nonEmptyString(source.groupId);
|
|
1103
|
-
if (groupId) return `group:${groupId}`;
|
|
1104
|
-
}
|
|
1105
|
-
if (source?.type === "room") {
|
|
1106
|
-
const roomId = nonEmptyString(source.roomId);
|
|
1107
|
-
if (roomId) return `room:${roomId}`;
|
|
1108
|
-
}
|
|
1109
|
-
if (source?.type === "user") {
|
|
1110
|
-
const userId = nonEmptyString(source.userId);
|
|
1111
|
-
if (userId) return `user:${userId}`;
|
|
1112
|
-
}
|
|
1113
|
-
return eventId;
|
|
1114
|
-
}
|
|
1115
|
-
function parseStoredEvent(rawEvent) {
|
|
1116
|
-
let event;
|
|
1117
|
-
try {
|
|
1118
|
-
event = JSON.parse(rawEvent);
|
|
1119
|
-
} catch (error) {
|
|
1120
|
-
throw new LineWebhookPayloadError("LINE webhook event JSON is invalid.", { cause: error });
|
|
1121
|
-
}
|
|
1122
|
-
return event;
|
|
1123
|
-
}
|
|
1124
|
-
function errorText(error) {
|
|
1125
|
-
return error instanceof Error ? error.message : String(error);
|
|
1126
|
-
}
|
|
1127
|
-
function isLineAuthenticationFailure(error) {
|
|
1128
|
-
if (!error || typeof error !== "object") return false;
|
|
1129
|
-
const status = error.status;
|
|
1130
|
-
return status === 401 || status === 403;
|
|
1131
|
-
}
|
|
1132
|
-
async function waitForActiveDeliveriesBeforeDispose(activeDeliveries) {
|
|
1133
|
-
let timeout;
|
|
1134
|
-
try {
|
|
1135
|
-
return await Promise.race([Promise.allSettled(activeDeliveries).then(() => true), new Promise((resolve) => {
|
|
1136
|
-
timeout = setTimeout(() => resolve(false), LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS);
|
|
1137
|
-
timeout.unref?.();
|
|
1138
|
-
})]);
|
|
1139
|
-
} finally {
|
|
1140
|
-
if (timeout) clearTimeout(timeout);
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
function createLineWebhookSpool(options) {
|
|
1144
|
-
const queue = options.queue ?? getLineRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
|
|
1145
|
-
const activeDeliveries = /* @__PURE__ */ new Set();
|
|
1146
|
-
const deferredClaims = /* @__PURE__ */ new Map();
|
|
1147
|
-
let acceptsDeferredClaims = true;
|
|
1148
|
-
const monitor = createChannelIngressMonitor({
|
|
1149
|
-
queue,
|
|
1150
|
-
inspect: ({ event }) => {
|
|
1151
|
-
const eventId = eventIdFor(event);
|
|
1152
|
-
return {
|
|
1153
|
-
eventId,
|
|
1154
|
-
laneKey: laneKeyFor(event, eventId)
|
|
1155
|
-
};
|
|
1156
|
-
},
|
|
1157
|
-
payload: {
|
|
1158
|
-
version: LINE_WEBHOOK_SPOOL_VERSION,
|
|
1159
|
-
serialize: ({ event, destination }) => ({
|
|
1160
|
-
rawEvent: JSON.stringify(event),
|
|
1161
|
-
destination
|
|
1162
|
-
}),
|
|
1163
|
-
deserialize: ({ rawEvent, destination }) => ({
|
|
1164
|
-
event: parseStoredEvent(rawEvent),
|
|
1165
|
-
destination
|
|
1166
|
-
}),
|
|
1167
|
-
encode: ({ version, body }) => ({
|
|
1168
|
-
version,
|
|
1169
|
-
rawEvent: body.rawEvent,
|
|
1170
|
-
destination: body.destination
|
|
1171
|
-
}),
|
|
1172
|
-
decode: (payload) => {
|
|
1173
|
-
if (typeof payload.rawEvent !== "string" || typeof payload.destination !== "string") throw new LineWebhookPayloadError("LINE webhook spool payload is invalid.");
|
|
1174
|
-
return {
|
|
1175
|
-
version: payload.version,
|
|
1176
|
-
body: {
|
|
1177
|
-
rawEvent: payload.rawEvent,
|
|
1178
|
-
destination: payload.destination
|
|
1179
|
-
}
|
|
1180
|
-
};
|
|
1181
|
-
},
|
|
1182
|
-
createClaimError: (kind) => new LineWebhookPayloadError(kind === "invalid-version" ? "LINE webhook spool payload is invalid." : "LINE webhook event identity changed after durable admission.")
|
|
1183
|
-
},
|
|
1184
|
-
deliver: async ({ event, destination }, lifecycle, claim) => {
|
|
1185
|
-
const boundLifecycle = bindIngressLifecycleToReplyOptions(lifecycle).turnAdoptionLifecycle;
|
|
1186
|
-
let handedOff = false;
|
|
1187
|
-
let resolveDeferredClaim;
|
|
1188
|
-
const deferredClaim = new Promise((resolve) => {
|
|
1189
|
-
resolveDeferredClaim = resolve;
|
|
1190
|
-
});
|
|
1191
|
-
let deferredClaimSettled = false;
|
|
1192
|
-
const settleDeferredClaim = () => {
|
|
1193
|
-
if (deferredClaimSettled) return;
|
|
1194
|
-
deferredClaimSettled = true;
|
|
1195
|
-
if (deferredClaims.get(claim.id) === deferredClaim) deferredClaims.delete(claim.id);
|
|
1196
|
-
resolveDeferredClaim();
|
|
1197
|
-
};
|
|
1198
|
-
const delivery = options.deliver(event, destination, { turnAdoptionLifecycle: {
|
|
1199
|
-
...boundLifecycle,
|
|
1200
|
-
onAdopted: async () => {
|
|
1201
|
-
handedOff = true;
|
|
1202
|
-
try {
|
|
1203
|
-
await boundLifecycle.onAdopted();
|
|
1204
|
-
} finally {
|
|
1205
|
-
settleDeferredClaim();
|
|
1206
|
-
}
|
|
1207
|
-
},
|
|
1208
|
-
onDeferred: () => {
|
|
1209
|
-
handedOff = true;
|
|
1210
|
-
if (!acceptsDeferredClaims) {
|
|
1211
|
-
settleDeferredClaim();
|
|
1212
|
-
Promise.resolve().then(() => boundLifecycle.onAbandoned()).catch((error) => {
|
|
1213
|
-
options.runtime.error?.(danger(`line: failed to abandon a late webhook delivery: ${errorText(error)}`));
|
|
1214
|
-
});
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
if (!deferredClaimSettled) deferredClaims.set(claim.id, deferredClaim);
|
|
1218
|
-
boundLifecycle.onDeferred();
|
|
1219
|
-
},
|
|
1220
|
-
onAbandoned: async () => {
|
|
1221
|
-
handedOff = true;
|
|
1222
|
-
try {
|
|
1223
|
-
await boundLifecycle.onAbandoned();
|
|
1224
|
-
} finally {
|
|
1225
|
-
settleDeferredClaim();
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
} });
|
|
1229
|
-
activeDeliveries.add(delivery);
|
|
1230
|
-
try {
|
|
1231
|
-
await delivery;
|
|
1232
|
-
} finally {
|
|
1233
|
-
activeDeliveries.delete(delivery);
|
|
1234
|
-
}
|
|
1235
|
-
if (stopTask && !handedOff) return {
|
|
1236
|
-
kind: "failed-retryable",
|
|
1237
|
-
error: /* @__PURE__ */ new Error("LINE webhook spool stopped before delivery handoff.")
|
|
1238
|
-
};
|
|
1239
|
-
},
|
|
1240
|
-
pollIntervalMs: LINE_WEBHOOK_DRAIN_INTERVAL_MS,
|
|
1241
|
-
retention: {
|
|
1242
|
-
pruneIntervalMs: 0,
|
|
1243
|
-
completedTtlMs: LINE_WEBHOOK_TOMBSTONE_TTL_MS,
|
|
1244
|
-
completedMaxEntries: LINE_WEBHOOK_TOMBSTONE_MAX_ENTRIES,
|
|
1245
|
-
failedTtlMs: LINE_WEBHOOK_TOMBSTONE_TTL_MS,
|
|
1246
|
-
failedMaxEntries: LINE_WEBHOOK_TOMBSTONE_MAX_ENTRIES
|
|
1247
|
-
},
|
|
1248
|
-
appendRetryDelaysMs: [0],
|
|
1249
|
-
waitForDeliveryIdleBeforeRepump: false,
|
|
1250
|
-
waitForDeliveryIdleOnStop: false,
|
|
1251
|
-
runPumpTask: runDetachedWebhookWork,
|
|
1252
|
-
admissionMode: "durable-after-stop",
|
|
1253
|
-
drain: {
|
|
1254
|
-
adoptionStallTimeoutMs: DEFAULT_INGRESS_ADOPTION_STALL_MS,
|
|
1255
|
-
orderBy: "received",
|
|
1256
|
-
scanLimit: LINE_WEBHOOK_DRAIN_SCAN_LIMIT,
|
|
1257
|
-
startLimit: LINE_WEBHOOK_MAX_CONCURRENT_DELIVERIES,
|
|
1258
|
-
retryPolicy: {
|
|
1259
|
-
maxAttempts: DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
|
|
1260
|
-
deadLetterMinAgeMs: 0
|
|
1261
|
-
},
|
|
1262
|
-
resolveNonRetryableFailure: (error) => {
|
|
1263
|
-
if (error instanceof LineWebhookPayloadError) return {
|
|
1264
|
-
reason: "invalid-event",
|
|
1265
|
-
message: error.message
|
|
1266
|
-
};
|
|
1267
|
-
if (error instanceof LineWebhookTerminalDeliveryError) return {
|
|
1268
|
-
reason: error.reason,
|
|
1269
|
-
message: error.message
|
|
1270
|
-
};
|
|
1271
|
-
if (isLineAuthenticationFailure(error)) return {
|
|
1272
|
-
reason: "authentication-failed",
|
|
1273
|
-
message: errorText(error)
|
|
1274
|
-
};
|
|
1275
|
-
return null;
|
|
1276
|
-
},
|
|
1277
|
-
onLog: (message) => options.runtime.error?.(danger(`line: ${message}`))
|
|
1278
|
-
},
|
|
1279
|
-
createStoppedError: () => /* @__PURE__ */ new Error("LINE webhook spool is stopped."),
|
|
1280
|
-
onError: (error) => options.runtime.error?.(danger(`line: webhook spool drain failed: ${errorText(error)}`))
|
|
1281
|
-
});
|
|
1282
|
-
let stopTask;
|
|
1283
|
-
return {
|
|
1284
|
-
accept: async (body) => {
|
|
1285
|
-
const events = body.events ?? [];
|
|
1286
|
-
if (events.length === 0) return;
|
|
1287
|
-
await monitor.admitBatch(events.map((event) => ({
|
|
1288
|
-
event,
|
|
1289
|
-
destination: body.destination ?? ""
|
|
1290
|
-
})), { receivedAt: Date.now() });
|
|
1291
|
-
},
|
|
1292
|
-
start: () => {
|
|
1293
|
-
if (!stopTask) monitor.start();
|
|
1294
|
-
},
|
|
1295
|
-
stop: () => {
|
|
1296
|
-
stopTask ??= (async () => {
|
|
1297
|
-
await monitor.pause();
|
|
1298
|
-
try {
|
|
1299
|
-
const deliveriesSettled = await waitForActiveDeliveriesBeforeDispose(activeDeliveries);
|
|
1300
|
-
if (!deliveriesSettled) options.runtime.log(warn(`line: timed out after ${LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS}ms waiting for active webhook deliveries; releasing drain ownership`));
|
|
1301
|
-
while (deferredClaims.size > 0) await Promise.allSettled(deferredClaims.values());
|
|
1302
|
-
acceptsDeferredClaims = false;
|
|
1303
|
-
if (deliveriesSettled) await monitor.waitForIdle();
|
|
1304
|
-
} finally {
|
|
1305
|
-
await monitor.stop();
|
|
1306
|
-
}
|
|
1307
|
-
})();
|
|
1308
|
-
return stopTask;
|
|
1309
|
-
}
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
//#endregion
|
|
1313
1007
|
//#region extensions/line/src/bot.ts
|
|
1314
1008
|
function createLineBot(opts) {
|
|
1315
1009
|
const runtime = opts.runtime ?? createNonExitingRuntime();
|
|
@@ -1322,26 +1016,24 @@ function createLineBot(opts) {
|
|
|
1322
1016
|
const processMessage = opts.onMessage ?? (async () => {
|
|
1323
1017
|
logVerbose("line: no message handler configured");
|
|
1324
1018
|
});
|
|
1019
|
+
const replayCache = createLineWebhookReplayCache();
|
|
1325
1020
|
const groupHistories = /* @__PURE__ */ new Map();
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
deliver: async (event, _destination, control) => await handleLineWebhookEvents([event], {
|
|
1021
|
+
const handleWebhook = async (body) => {
|
|
1022
|
+
if (!body.events || body.events.length === 0) return;
|
|
1023
|
+
await handleLineWebhookEvents(body.events, {
|
|
1330
1024
|
cfg,
|
|
1331
1025
|
account,
|
|
1332
1026
|
runtime,
|
|
1333
1027
|
mediaMaxBytes,
|
|
1334
1028
|
processMessage,
|
|
1335
|
-
|
|
1029
|
+
replayCache,
|
|
1336
1030
|
groupHistories,
|
|
1337
1031
|
historyLimit: cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
|
|
1338
|
-
})
|
|
1339
|
-
}
|
|
1340
|
-
spool.start();
|
|
1032
|
+
});
|
|
1033
|
+
};
|
|
1341
1034
|
return {
|
|
1342
|
-
handleWebhook
|
|
1343
|
-
account
|
|
1344
|
-
stop: spool.stop
|
|
1035
|
+
handleWebhook,
|
|
1036
|
+
account
|
|
1345
1037
|
};
|
|
1346
1038
|
}
|
|
1347
1039
|
//#endregion
|
|
@@ -1359,9 +1051,63 @@ function resolveLineDurableReplyOptions(params) {
|
|
|
1359
1051
|
return { to: params.to };
|
|
1360
1052
|
}
|
|
1361
1053
|
//#endregion
|
|
1054
|
+
//#region extensions/line/src/reply-chunks.ts
|
|
1055
|
+
async function sendLineReplyChunks(params) {
|
|
1056
|
+
const hasQuickReplies = Boolean(params.quickReplies?.length);
|
|
1057
|
+
let replyTokenUsed = Boolean(params.replyTokenUsed);
|
|
1058
|
+
if (params.chunks.length === 0) return { replyTokenUsed };
|
|
1059
|
+
if (params.replyToken && !replyTokenUsed) try {
|
|
1060
|
+
const replyBatch = params.chunks.slice(0, 5);
|
|
1061
|
+
const remaining = params.chunks.slice(replyBatch.length);
|
|
1062
|
+
const replyMessages = replyBatch.map((chunk) => ({
|
|
1063
|
+
type: "text",
|
|
1064
|
+
text: chunk
|
|
1065
|
+
}));
|
|
1066
|
+
if (hasQuickReplies && remaining.length === 0 && replyMessages.length > 0) {
|
|
1067
|
+
const lastIndex = replyMessages.length - 1;
|
|
1068
|
+
replyMessages[lastIndex] = params.createTextMessageWithQuickReplies(replyBatch[lastIndex], params.quickReplies);
|
|
1069
|
+
}
|
|
1070
|
+
await params.replyMessageLine(params.replyToken, replyMessages, {
|
|
1071
|
+
cfg: params.cfg,
|
|
1072
|
+
accountId: params.accountId
|
|
1073
|
+
});
|
|
1074
|
+
replyTokenUsed = true;
|
|
1075
|
+
for (let i = 0; i < remaining.length; i += 1) if (i === remaining.length - 1 && hasQuickReplies) await params.pushTextMessageWithQuickReplies(params.to, remaining[i], params.quickReplies, {
|
|
1076
|
+
cfg: params.cfg,
|
|
1077
|
+
accountId: params.accountId
|
|
1078
|
+
});
|
|
1079
|
+
else await params.pushMessageLine(params.to, remaining[i], {
|
|
1080
|
+
cfg: params.cfg,
|
|
1081
|
+
accountId: params.accountId
|
|
1082
|
+
});
|
|
1083
|
+
return { replyTokenUsed };
|
|
1084
|
+
} catch (err) {
|
|
1085
|
+
params.onReplyError?.(err);
|
|
1086
|
+
replyTokenUsed = true;
|
|
1087
|
+
}
|
|
1088
|
+
for (let i = 0; i < params.chunks.length; i += 1) if (i === params.chunks.length - 1 && hasQuickReplies) await params.pushTextMessageWithQuickReplies(params.to, params.chunks[i], params.quickReplies, {
|
|
1089
|
+
cfg: params.cfg,
|
|
1090
|
+
accountId: params.accountId
|
|
1091
|
+
});
|
|
1092
|
+
else await params.pushMessageLine(params.to, params.chunks[i], {
|
|
1093
|
+
cfg: params.cfg,
|
|
1094
|
+
accountId: params.accountId
|
|
1095
|
+
});
|
|
1096
|
+
return { replyTokenUsed };
|
|
1097
|
+
}
|
|
1098
|
+
//#endregion
|
|
1362
1099
|
//#region extensions/line/src/signature.ts
|
|
1363
1100
|
function validateLineSignature(body, signature, channelSecret) {
|
|
1364
|
-
|
|
1101
|
+
const hash = crypto.createHmac("SHA256", channelSecret).update(body).digest("base64");
|
|
1102
|
+
const hashBuffer = Buffer.from(hash);
|
|
1103
|
+
const signatureBuffer = Buffer.from(signature);
|
|
1104
|
+
const maxLen = Math.max(hashBuffer.length, signatureBuffer.length);
|
|
1105
|
+
const paddedHash = Buffer.alloc(maxLen);
|
|
1106
|
+
const paddedSig = Buffer.alloc(maxLen);
|
|
1107
|
+
hashBuffer.copy(paddedHash);
|
|
1108
|
+
signatureBuffer.copy(paddedSig);
|
|
1109
|
+
const timingResult = crypto.timingSafeEqual(paddedHash, paddedSig);
|
|
1110
|
+
return hashBuffer.length === signatureBuffer.length && timingResult;
|
|
1365
1111
|
}
|
|
1366
1112
|
//#endregion
|
|
1367
1113
|
//#region extensions/line/src/webhook-utils.ts
|
|
@@ -1374,8 +1120,8 @@ function parseLineWebhookBody(rawBody) {
|
|
|
1374
1120
|
}
|
|
1375
1121
|
//#endregion
|
|
1376
1122
|
//#region extensions/line/src/webhook-node.ts
|
|
1377
|
-
const LINE_WEBHOOK_MAX_BODY_BYTES =
|
|
1378
|
-
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES$1 =
|
|
1123
|
+
const LINE_WEBHOOK_MAX_BODY_BYTES = 1048576;
|
|
1124
|
+
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES$1 = 65536;
|
|
1379
1125
|
const LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS$1 = 5e3;
|
|
1380
1126
|
async function readLineWebhookRequestBody(req, maxBytes = LINE_WEBHOOK_MAX_BODY_BYTES, timeoutMs = LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS$1) {
|
|
1381
1127
|
return await readRequestBodyWithLimit(req, {
|
|
@@ -1383,6 +1129,9 @@ async function readLineWebhookRequestBody(req, maxBytes = LINE_WEBHOOK_MAX_BODY_
|
|
|
1383
1129
|
timeoutMs
|
|
1384
1130
|
});
|
|
1385
1131
|
}
|
|
1132
|
+
function logLineWebhookDispatchError(runtime, err) {
|
|
1133
|
+
runtime?.error?.(danger(`line webhook dispatch failed: ${String(err)}`));
|
|
1134
|
+
}
|
|
1386
1135
|
function createLineNodeWebhookHandler(params) {
|
|
1387
1136
|
const maxBodyBytes = params.maxBodyBytes ?? LINE_WEBHOOK_MAX_BODY_BYTES;
|
|
1388
1137
|
const readBody = params.readBody ?? readLineWebhookRequestBody;
|
|
@@ -1405,6 +1154,7 @@ function createLineNodeWebhookHandler(params) {
|
|
|
1405
1154
|
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
1406
1155
|
return;
|
|
1407
1156
|
}
|
|
1157
|
+
let receiveContext;
|
|
1408
1158
|
try {
|
|
1409
1159
|
const signatureHeader = req.headers["x-line-signature"];
|
|
1410
1160
|
const signature = typeof signatureHeader === "string" ? signatureHeader.trim() : Array.isArray(signatureHeader) ? (signatureHeader[0] ?? "").trim() : "";
|
|
@@ -1431,14 +1181,24 @@ function createLineNodeWebhookHandler(params) {
|
|
|
1431
1181
|
return;
|
|
1432
1182
|
}
|
|
1433
1183
|
params.onRequestAuthenticated?.();
|
|
1184
|
+
receiveContext = createMessageReceiveContext({
|
|
1185
|
+
id: `${Date.now()}:line:webhook`,
|
|
1186
|
+
channel: "line",
|
|
1187
|
+
message: body,
|
|
1188
|
+
ackPolicy: "after_receive_record",
|
|
1189
|
+
onAck: () => {
|
|
1190
|
+
res.statusCode = 200;
|
|
1191
|
+
res.setHeader("Content-Type", "application/json");
|
|
1192
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
if (receiveContext.shouldAckAfter("receive_record")) await receiveContext.ack();
|
|
1434
1196
|
if (body.events && body.events.length > 0) {
|
|
1435
1197
|
logVerbose(`line: received ${body.events.length} webhook events`);
|
|
1436
|
-
|
|
1198
|
+
Promise.resolve().then(() => params.bot.handleWebhook(body)).catch((err) => logLineWebhookDispatchError(params.runtime, err));
|
|
1437
1199
|
}
|
|
1438
|
-
res.statusCode = 200;
|
|
1439
|
-
res.setHeader("Content-Type", "application/json");
|
|
1440
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
1441
1200
|
} catch (err) {
|
|
1201
|
+
await receiveContext?.nack(err);
|
|
1442
1202
|
if (isRequestBodyLimitError$1(err, "PAYLOAD_TOO_LARGE")) {
|
|
1443
1203
|
res.statusCode = 413;
|
|
1444
1204
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -1462,10 +1222,24 @@ function createLineNodeWebhookHandler(params) {
|
|
|
1462
1222
|
}
|
|
1463
1223
|
//#endregion
|
|
1464
1224
|
//#region extensions/line/src/monitor.ts
|
|
1225
|
+
const runtimeState = /* @__PURE__ */ new Map();
|
|
1465
1226
|
const lineWebhookInFlightLimiter = createWebhookInFlightLimiter();
|
|
1466
|
-
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES =
|
|
1227
|
+
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES = 65536;
|
|
1467
1228
|
const LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS = 5e3;
|
|
1468
1229
|
const lineWebhookTargets = /* @__PURE__ */ new Map();
|
|
1230
|
+
function recordChannelRuntimeState(params) {
|
|
1231
|
+
const key = `${params.channel}:${params.accountId}`;
|
|
1232
|
+
const existing = runtimeState.get(key) ?? {
|
|
1233
|
+
running: false,
|
|
1234
|
+
lastStartAt: null,
|
|
1235
|
+
lastStopAt: null,
|
|
1236
|
+
lastError: null
|
|
1237
|
+
};
|
|
1238
|
+
runtimeState.set(key, {
|
|
1239
|
+
...existing,
|
|
1240
|
+
...params.state
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1469
1243
|
function startLineLoadingKeepalive(params) {
|
|
1470
1244
|
const intervalMs = params.intervalMs ?? 18e3;
|
|
1471
1245
|
const loadingSeconds = params.loadingSeconds ?? 20;
|
|
@@ -1499,9 +1273,14 @@ async function monitorLineProvider(opts) {
|
|
|
1499
1273
|
accountId,
|
|
1500
1274
|
runtime,
|
|
1501
1275
|
config,
|
|
1502
|
-
onMessage: async (ctx
|
|
1276
|
+
onMessage: async (ctx) => {
|
|
1503
1277
|
if (!ctx) return;
|
|
1504
1278
|
const { ctxPayload, replyToken, route } = ctx;
|
|
1279
|
+
recordChannelRuntimeState({
|
|
1280
|
+
channel: "line",
|
|
1281
|
+
accountId: resolvedAccountId,
|
|
1282
|
+
state: { lastInboundAt: Date.now() }
|
|
1283
|
+
});
|
|
1505
1284
|
const shouldShowLoading = Boolean(ctx.userId && !ctx.isGroup);
|
|
1506
1285
|
const displayNamePromise = ctx.userId ? getUserDisplayName(ctx.userId, {
|
|
1507
1286
|
cfg: config,
|
|
@@ -1512,24 +1291,16 @@ async function monitorLineProvider(opts) {
|
|
|
1512
1291
|
userId: ctx.userId,
|
|
1513
1292
|
accountId: ctx.accountId
|
|
1514
1293
|
}) : null;
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
let turnAdopted = false;
|
|
1518
|
-
const ingressLifecycle = deliveryControl.turnAdoptionLifecycle;
|
|
1294
|
+
const displayName = await displayNamePromise;
|
|
1295
|
+
logVerbose(`line: received message from ${displayName} (${ctxPayload.From})`);
|
|
1519
1296
|
try {
|
|
1520
1297
|
const textLimit = 5e3;
|
|
1521
|
-
|
|
1298
|
+
let replyTokenUsed = false;
|
|
1299
|
+
const core = getLineRuntime();
|
|
1300
|
+
const turnResult = await core.channel.inbound.run({
|
|
1522
1301
|
channel: "line",
|
|
1523
1302
|
accountId: route.accountId,
|
|
1524
1303
|
raw: ctx,
|
|
1525
|
-
turnAdoptionLifecycle: {
|
|
1526
|
-
...ingressLifecycle,
|
|
1527
|
-
admission: "exclusive",
|
|
1528
|
-
onAdopted: async () => {
|
|
1529
|
-
await ingressLifecycle?.onAdopted();
|
|
1530
|
-
turnAdopted = true;
|
|
1531
|
-
}
|
|
1532
|
-
},
|
|
1533
1304
|
adapter: {
|
|
1534
1305
|
ingest: () => ({
|
|
1535
1306
|
id: ctxPayload.MessageSid ?? `${ctxPayload.From}:${Date.now()}`,
|
|
@@ -1539,14 +1310,14 @@ async function monitorLineProvider(opts) {
|
|
|
1539
1310
|
cfg: config,
|
|
1540
1311
|
channel: "line",
|
|
1541
1312
|
accountId: route.accountId,
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
},
|
|
1313
|
+
agentId: route.agentId,
|
|
1314
|
+
routeSessionKey: route.sessionKey,
|
|
1315
|
+
storePath: ctx.turn.storePath,
|
|
1546
1316
|
ctxPayload,
|
|
1317
|
+
recordInboundSession: core.channel.session.recordInboundSession,
|
|
1318
|
+
dispatchReplyWithBufferedBlockDispatcher: core.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
|
|
1547
1319
|
record: ctx.turn.record,
|
|
1548
1320
|
replyPipeline: {},
|
|
1549
|
-
...ingressLifecycle?.abortSignal ? { replyOptions: { abortSignal: ingressLifecycle.abortSignal } } : {},
|
|
1550
1321
|
delivery: {
|
|
1551
1322
|
durable: (payload, info) => resolveLineDurableReplyOptions({
|
|
1552
1323
|
payload,
|
|
@@ -1574,11 +1345,15 @@ async function monitorLineProvider(opts) {
|
|
|
1574
1345
|
buildTemplateMessageFromPayload,
|
|
1575
1346
|
processLineMessage,
|
|
1576
1347
|
chunkMarkdownText,
|
|
1348
|
+
sendLineReplyChunks,
|
|
1577
1349
|
replyMessageLine,
|
|
1350
|
+
pushMessageLine,
|
|
1351
|
+
pushTextMessageWithQuickReplies,
|
|
1578
1352
|
createQuickReplyItems,
|
|
1353
|
+
createTextMessageWithQuickReplies,
|
|
1579
1354
|
pushMessagesLine,
|
|
1580
1355
|
createFlexMessage,
|
|
1581
|
-
|
|
1356
|
+
createImageMessage,
|
|
1582
1357
|
createLocationMessage,
|
|
1583
1358
|
onReplyError: (replyErr) => {
|
|
1584
1359
|
logVerbose(`line: reply token failed, falling back to push: ${String(replyErr)}`);
|
|
@@ -1587,7 +1362,11 @@ async function monitorLineProvider(opts) {
|
|
|
1587
1362
|
});
|
|
1588
1363
|
replyTokenUsed = deliveryResult.replyTokenUsed;
|
|
1589
1364
|
if (deliveryResult.status === "partial") throw deliveryResult.error;
|
|
1590
|
-
|
|
1365
|
+
recordChannelRuntimeState({
|
|
1366
|
+
channel: "line",
|
|
1367
|
+
accountId: resolvedAccountId,
|
|
1368
|
+
state: { lastOutboundAt: Date.now() }
|
|
1369
|
+
});
|
|
1591
1370
|
},
|
|
1592
1371
|
onError: (err, info) => {
|
|
1593
1372
|
runtime.error?.(danger(`line ${info.kind} reply failed: ${String(err)}`));
|
|
@@ -1596,11 +1375,21 @@ async function monitorLineProvider(opts) {
|
|
|
1596
1375
|
})
|
|
1597
1376
|
}
|
|
1598
1377
|
});
|
|
1599
|
-
|
|
1378
|
+
const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
|
|
1379
|
+
if (!hasFinalInboundReplyDispatch(dispatchResult)) logVerbose(`line: no response generated for message from ${ctxPayload.From}`);
|
|
1600
1380
|
} catch (err) {
|
|
1601
1381
|
runtime.error?.(danger(`line: auto-reply failed: ${String(err)}`));
|
|
1602
|
-
if (
|
|
1603
|
-
|
|
1382
|
+
if (replyToken) try {
|
|
1383
|
+
await replyMessageLine(replyToken, [{
|
|
1384
|
+
type: "text",
|
|
1385
|
+
text: "Sorry, I encountered an error processing your message."
|
|
1386
|
+
}], {
|
|
1387
|
+
cfg: config,
|
|
1388
|
+
accountId: ctx.accountId
|
|
1389
|
+
});
|
|
1390
|
+
} catch (replyErr) {
|
|
1391
|
+
runtime.error?.(danger(`line: error reply failed: ${String(replyErr)}`));
|
|
1392
|
+
}
|
|
1604
1393
|
} finally {
|
|
1605
1394
|
stopLoading?.();
|
|
1606
1395
|
}
|
|
@@ -1678,13 +1467,16 @@ async function monitorLineProvider(opts) {
|
|
|
1678
1467
|
res.end(JSON.stringify({ error: "Invalid webhook payload" }));
|
|
1679
1468
|
return;
|
|
1680
1469
|
}
|
|
1681
|
-
|
|
1682
|
-
logVerbose(`line: received ${body.events.length} webhook events`);
|
|
1683
|
-
await match.target.bot.handleWebhook(body);
|
|
1684
|
-
}
|
|
1470
|
+
requestLifecycle.release();
|
|
1685
1471
|
res.statusCode = 200;
|
|
1686
1472
|
res.setHeader("Content-Type", "application/json");
|
|
1687
1473
|
res.end(JSON.stringify({ status: "ok" }));
|
|
1474
|
+
if (body.events && body.events.length > 0) {
|
|
1475
|
+
logVerbose(`line: received ${body.events.length} webhook events`);
|
|
1476
|
+
Promise.resolve().then(() => match.target.bot.handleWebhook(body)).catch((err) => {
|
|
1477
|
+
match.target.runtime.error?.(danger(`line webhook dispatch failed: ${String(err)}`));
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1688
1480
|
} catch (err) {
|
|
1689
1481
|
if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
|
|
1690
1482
|
res.statusCode = 413;
|
|
@@ -1710,31 +1502,41 @@ async function monitorLineProvider(opts) {
|
|
|
1710
1502
|
}
|
|
1711
1503
|
}
|
|
1712
1504
|
});
|
|
1505
|
+
recordChannelRuntimeState({
|
|
1506
|
+
channel: "line",
|
|
1507
|
+
accountId: resolvedAccountId,
|
|
1508
|
+
state: {
|
|
1509
|
+
running: true,
|
|
1510
|
+
lastStartAt: Date.now()
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1713
1513
|
logVerbose(`line: registered webhook handler at ${normalizedPath}`);
|
|
1714
1514
|
let stopped = false;
|
|
1715
|
-
let stopPromise;
|
|
1716
1515
|
const stopHandler = () => {
|
|
1717
|
-
if (
|
|
1718
|
-
if (stopped) return Promise.resolve();
|
|
1516
|
+
if (stopped) return;
|
|
1719
1517
|
stopped = true;
|
|
1720
1518
|
logVerbose(`line: stopping provider for account ${resolvedAccountId}`);
|
|
1721
1519
|
unregisterHttp();
|
|
1722
|
-
|
|
1723
|
-
|
|
1520
|
+
recordChannelRuntimeState({
|
|
1521
|
+
channel: "line",
|
|
1522
|
+
accountId: resolvedAccountId,
|
|
1523
|
+
state: {
|
|
1524
|
+
running: false,
|
|
1525
|
+
lastStopAt: Date.now()
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1724
1528
|
};
|
|
1725
|
-
|
|
1726
|
-
if (abortSignal?.aborted) await stopHandler();
|
|
1529
|
+
if (abortSignal?.aborted) stopHandler();
|
|
1727
1530
|
else if (abortSignal) {
|
|
1728
|
-
abortSignal.addEventListener("abort",
|
|
1531
|
+
abortSignal.addEventListener("abort", stopHandler, { once: true });
|
|
1729
1532
|
await waitForAbortSignal(abortSignal);
|
|
1730
|
-
await stopHandler();
|
|
1731
1533
|
}
|
|
1732
1534
|
return {
|
|
1733
1535
|
account: bot.account,
|
|
1734
1536
|
handleWebhook: bot.handleWebhook,
|
|
1735
|
-
stop:
|
|
1736
|
-
|
|
1737
|
-
abortSignal?.removeEventListener("abort",
|
|
1537
|
+
stop: () => {
|
|
1538
|
+
stopHandler();
|
|
1539
|
+
abortSignal?.removeEventListener("abort", stopHandler);
|
|
1738
1540
|
}
|
|
1739
1541
|
};
|
|
1740
1542
|
}
|