feat: epro_ig_demo 常开 IG 条款同意,闭环 ABP 补上 Agree 按钮
- db/ixserver/loop_patch.sql:epro_ig_demo 写 T_and_C_Setting(NeedSendAfterMinutes 10080), 条款流程对第三方 chatbot 与人工状态都生效 - db/abp/loop_patch.sql 第 3 节:把模板「Agreement Button (Line IG)」追加到 epro_ig_demo 条款组第二位。staging 的 IG 条款组漏了这条(Line 从问候模板按位置切、带着它), 用户收到条款无按钮可点;闭环在这一处与 staging 不同。补丁可重复执行,末尾列出条款组 - scenarios/_lib.ps1:Complete-LoopTnC 替新用户走完条款 → 同意 → 选语言 → 问候, 有 Agree 按钮就点按钮、没有退回裸 payload postback;默认选 English,保持 S5 的 Yes 问句 - S1 改为每次新 IGSID;S1 / S5 / S8 开头先走条款同意 - README 说明条款常开与 ABP 补丁 winbox 上 reset + reset-abp -Init 后 S1 / S5 / S8 均通过。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>main
parent
e7f656cb99
commit
327dee9f33
|
|
@ -62,6 +62,11 @@ S5 依赖 `epro_ig_demo` 的 `DisconnectChatbotFlowSetting` 已打开(`db\ixse
|
||||||
|
|
||||||
跑的时候开着观察台 <http://127.0.0.1:5090>,每一跳和状态变化会实时画成泳道时序图;`?ticket=<工单号>` 只看一张。
|
跑的时候开着观察台 <http://127.0.0.1:5090>,每一跳和状态变化会实时画成泳道时序图;`?ticket=<工单号>` 只看一张。
|
||||||
|
|
||||||
|
`epro_ig_demo` 常开条款同意(`db\ixserver\loop_patch.sql` 第 3 节的 `T_and_C_Setting`):
|
||||||
|
新 IG 用户的第一句话会被条款拦下,三个场景都先调 `_lib.ps1` 的 `Complete-LoopTnC` 走完「条款 → 同意 → 选语言 → 问候」。
|
||||||
|
staging 的 IG 条款组**缺 Agree 按钮**(漏了 Line 问候里的「Agreement Button (Line IG)」);闭环 ABP 在
|
||||||
|
`db\abp\loop_patch.sql` 第 3 节补上了,这一处与 staging 不同。改过这个补丁要 `.\loop.ps1 reset-abp -Init` 重新打并留底。
|
||||||
|
|
||||||
S5 / S8 结束时(失败也一样)会把服务号切回机器人、在线人数归零,所以几个场景先后跑互不影响;要停在人工状态看页面加 `-KeepShift`。
|
S5 / S8 结束时(失败也一样)会把服务号切回机器人、在线人数归零,所以几个场景先后跑互不影响;要停在人工状态看页面加 `-KeepShift`。
|
||||||
|
|
||||||
从 Mac 看几个页面用 SSH 本地端口转发:
|
从 Mac 看几个页面用 SSH 本地端口转发:
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@
|
||||||
-- FX_App 的 client secret → loop-fxapp-secret
|
-- FX_App 的 client secret → loop-fxapp-secret
|
||||||
-- admin 的密码(iXserver 用 FX_App + password 授权取模板)→ Loop@123456
|
-- admin 的密码(iXserver 用 FX_App + password 授权取模板)→ Loop@123456
|
||||||
-- 明文都在 config\loop.psd1 里,是闭环专用的假值。
|
-- 明文都在 config\loop.psd1 里,是闭环专用的假值。
|
||||||
|
--
|
||||||
|
-- 另外改一处模板配置(第 3 节):epro_ig_demo 的条款组补上「Agreement Button (Line IG)」。
|
||||||
|
-- 整段可重复执行,结果不变。
|
||||||
|
|
||||||
SET NOCOUNT ON;
|
SET NOCOUNT ON;
|
||||||
SET XACT_ABORT ON;
|
SET XACT_ABORT ON;
|
||||||
|
|
@ -46,9 +49,41 @@ UPDATE dbo.AbpUsers
|
||||||
LockoutEnd = NULL
|
LockoutEnd = NULL
|
||||||
WHERE UserName = N'admin' AND TenantId IS NULL;
|
WHERE UserName = N'admin' AND TenantId IS NULL;
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------------------------
|
||||||
|
-- 3. epro_ig_demo 的条款组补上 Agree 按钮
|
||||||
|
-- Line 从问候模板按位置切条款:[Agreement (Line IG), Agreement Button (Line IG), Agreed, Select Language, …];
|
||||||
|
-- IG 走条款专用分组,从 staging 拷来时 AgreementMessageTemplateIdsJson 只有 Agreement (Line IG),
|
||||||
|
-- 缺了第二条「Please click Agree」,用户收到条款没有按钮可点。这里照 Line 的顺序追加到第二位。
|
||||||
|
-- staging 本身没改,闭环在这一处与 staging 不同
|
||||||
|
------------------------------------------------------------------------------------------------
|
||||||
|
DECLARE @agreeButton NVARCHAR(36) = (SELECT LOWER(CONVERT(NVARCHAR(36), Id)) FROM dbo.iCON_MT_MessageTemplates
|
||||||
|
WHERE Name = N'Agreement Button (Line IG)' AND IsDeleted = 0);
|
||||||
|
IF @agreeButton IS NULL THROW 50004, N'iCONAbpFX 里没有模板 Agreement Button (Line IG)', 1;
|
||||||
|
|
||||||
|
DECLARE @igReply UNIQUEIDENTIFIER = (SELECT s.Id FROM dbo.iCON_Chat_AutoReplySettings s
|
||||||
|
JOIN dbo.iCON_Chat_ChatServiceAccounts a ON a.Id = s.ChatServiceAccountId
|
||||||
|
WHERE a.Code = N'epro_ig_demo' AND a.IsDeleted = 0 AND s.IsDeleted = 0 AND s.Name = N'Default');
|
||||||
|
IF @igReply IS NULL THROW 50005, N'iCONAbpFX 里没有 epro_ig_demo 的 Default 自动回复设置', 1;
|
||||||
|
|
||||||
|
UPDATE dbo.iCON_Chat_AutoReplySettings
|
||||||
|
SET AgreementMessageTemplateIdsJson = JSON_MODIFY(AgreementMessageTemplateIdsJson, 'append $', @agreeButton),
|
||||||
|
ConcurrencyStamp = REPLACE(CONVERT(NVARCHAR(36), NEWID()), '-', ''),
|
||||||
|
LastModificationTime = GETDATE()
|
||||||
|
WHERE Id = @igReply
|
||||||
|
AND ISJSON(AgreementMessageTemplateIdsJson) = 1
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM OPENJSON(AgreementMessageTemplateIdsJson) WHERE LOWER(value) = @agreeButton);
|
||||||
|
|
||||||
COMMIT TRANSACTION;
|
COMMIT TRANSACTION;
|
||||||
|
|
||||||
SELECT c.ClientId, c.Enabled, c.AccessTokenLifetime,
|
SELECT c.ClientId, c.Enabled, c.AccessTokenLifetime,
|
||||||
(SELECT COUNT(*) FROM dbo.IdentityServerClientSecrets s WHERE s.ClientId = c.Id) AS secrets
|
(SELECT COUNT(*) FROM dbo.IdentityServerClientSecrets s WHERE s.ClientId = c.Id) AS secrets
|
||||||
FROM dbo.IdentityServerClients c
|
FROM dbo.IdentityServerClients c
|
||||||
WHERE c.ClientId IN (N'setsail', N'FX_App');
|
WHERE c.ClientId IN (N'setsail', N'FX_App');
|
||||||
|
|
||||||
|
SELECT a.Code, t.[key] AS position, m.Name AS agreement_template
|
||||||
|
FROM dbo.iCON_Chat_AutoReplySettings s
|
||||||
|
JOIN dbo.iCON_Chat_ChatServiceAccounts a ON a.Id = s.ChatServiceAccountId
|
||||||
|
CROSS APPLY OPENJSON(s.AgreementMessageTemplateIdsJson) t
|
||||||
|
LEFT JOIN dbo.iCON_MT_MessageTemplates m ON LOWER(CONVERT(NVARCHAR(36), m.Id)) = LOWER(t.value)
|
||||||
|
WHERE s.Id = @igReply
|
||||||
|
ORDER BY CONVERT(INT, t.[key]);
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,14 @@ UPDATE dbo.service_account_config
|
||||||
"NeedReactiveAfterIgnore": false, "NeedGreetingForNewTicket": false }'))
|
"NeedReactiveAfterIgnore": false, "NeedGreetingForNewTicket": false }'))
|
||||||
WHERE code = N'epro_ig_demo';
|
WHERE code = N'epro_ig_demo';
|
||||||
|
|
||||||
|
-- 条款同意(docs\IG 条款同意流程方案.html):存了 T_and_C_Setting 且 ABP 模板里有条款才启用,两样缺一就静默不启用。
|
||||||
|
-- 模板用本地 ABP 的 epro_ig_demo,所以这里只开设置;间隔取代码默认的 10080 分钟(7 天)。
|
||||||
|
-- 对第三方 chatbot 服务号同样生效:新用户第一句话被条款拦下,场景先调 scenarios\_lib.ps1 的 Complete-LoopTnC 走完同意。
|
||||||
|
-- staging 的条款组缺「请点 Agree」那条、没按钮可点;闭环 ABP 已补上(db\abp\loop_patch.sql 第 3 节)
|
||||||
|
UPDATE dbo.service_account_config
|
||||||
|
SET all_setting = JSON_MODIFY(all_setting, '$.T_and_C_Setting', JSON_QUERY(N'{ "NeedSendAfterMinutes": 10080 }'))
|
||||||
|
WHERE code = N'epro_ig_demo';
|
||||||
|
|
||||||
------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------
|
||||||
-- 4. 第三方 client 登记:两张表都要有 setsail(方案 §2 链路 c)。ip_whitelist 留空 = 不限来源
|
-- 4. 第三方 client 登记:两张表都要有 setsail(方案 §2 链路 c)。ip_whitelist 留空 = 不限来源
|
||||||
------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -108,5 +116,6 @@ SELECT code,
|
||||||
JSON_VALUE(all_setting, '$.AISetting.AIProviderWebhook.Webhook') AS webhook,
|
JSON_VALUE(all_setting, '$.AISetting.AIProviderWebhook.Webhook') AS webhook,
|
||||||
JSON_VALUE(all_setting, '$.ChatMessageSetting.CurrentProviderKey') AS message_provider,
|
JSON_VALUE(all_setting, '$.ChatMessageSetting.CurrentProviderKey') AS message_provider,
|
||||||
JSON_VALUE(all_setting, '$.InstagramSetting.InstagramAccount') AS ig_account,
|
JSON_VALUE(all_setting, '$.InstagramSetting.InstagramAccount') AS ig_account,
|
||||||
JSON_VALUE(all_setting, '$.InstagramSetting.FanPageToken') AS ig_token
|
JSON_VALUE(all_setting, '$.InstagramSetting.FanPageToken') AS ig_token,
|
||||||
|
JSON_VALUE(all_setting, '$.T_and_C_Setting.NeedSendAfterMinutes') AS tnc_minutes
|
||||||
FROM dbo.service_account_config WHERE code = N'epro_ig_demo';
|
FROM dbo.service_account_config WHERE code = N'epro_ig_demo';
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,80 @@ function Save-LoopReport([bool]$Passed, [string]$Reason = '') {
|
||||||
Write-Host "没有生成报告(观察台没在跑?.\loop.ps1 up -Only console):$($_.Exception.Message)" -ForegroundColor Yellow
|
Write-Host "没有生成报告(观察台没在跑?.\loop.ps1 up -Only console):$($_.Exception.Message)" -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================================ 条款同意
|
||||||
|
|
||||||
|
# 以这位用户身份发一条入站;iXserver 没收下返回 $null
|
||||||
|
function Send-LoopIg([string]$Igsid, [hashtable]$Body) {
|
||||||
|
$Body.igsid = $Igsid
|
||||||
|
$bytes = [Text.Encoding]::UTF8.GetBytes(($Body | ConvertTo-Json -Compress))
|
||||||
|
try { $r = Invoke-RestMethod "http://127.0.0.1:$($LoopCfg.Ports.IgMock)/_api/send" -Method Post -ContentType 'application/json; charset=utf-8' -Body $bytes }
|
||||||
|
catch { return $null } # IG 模拟投递失败回 502
|
||||||
|
if ($r.event -and $r.event.response.status -eq 200) { return $r.event }
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# 这位用户在某个入站事件之后收到的消息(IG 模拟的事件 id 全局递增)
|
||||||
|
function Get-LoopOutbound([string]$Igsid, [int]$AfterId) {
|
||||||
|
@((Invoke-RestMethod "http://127.0.0.1:$($LoopCfg.Ports.IgMock)/_api/state").events |
|
||||||
|
Where-Object { $_.kind -eq 'outbound' -and $_.igsid -eq $Igsid -and $_.id -gt $AfterId } | Sort-Object id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Wait-LoopOutbound([string]$Igsid, [int]$AfterId, [scriptblock]$Match, [int]$TimeoutSec) {
|
||||||
|
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||||||
|
while ((Get-Date) -lt $deadline) {
|
||||||
|
$hit = @(Get-LoopOutbound $Igsid $AfterId | Where-Object -FilterScript $Match) | Select-Object -First 1
|
||||||
|
if ($hit) { return $hit }
|
||||||
|
Start-Sleep -Milliseconds 300
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# 一条出站消息上用户能点的:按钮模板 / 卡片的 postback 按钮,或 quick reply
|
||||||
|
function Get-LoopChoices($Outbound) {
|
||||||
|
$m = $Outbound.body.message
|
||||||
|
if ($m.attachment -and $m.attachment.type -eq 'template') {
|
||||||
|
$p = $m.attachment.payload
|
||||||
|
$buttons = if ($p.buttons) { @($p.buttons) } else { @($p.elements | ForEach-Object { $_.buttons }) }
|
||||||
|
return @($buttons | Where-Object { $_ -and $_.type -eq 'postback' } | ForEach-Object { @{ kind = 'postback'; title = $_.title; payload = $_.payload } })
|
||||||
|
}
|
||||||
|
return @($m.quick_replies | Where-Object { $_ } | ForEach-Object { @{ kind = 'quick_reply'; title = $_.title; payload = $_.payload } })
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
epro_ig_demo 常开条款流程(db\ixserver\loop_patch.sql 第 3 节):新 IG 用户的第一句话被条款拦下,
|
||||||
|
不进第三方也不进人工对话。场景先走完「发一句 → 收条款 → 同意 → 选语言 → 收问候」,再测自己的链路。
|
||||||
|
条款里有 Agree 按钮就照按钮的形态点(闭环 ABP 已补上,db\abp\loop_patch.sql 第 3 节);
|
||||||
|
没有时(库是 staging 原样、没打那一节)退回发裸 payload 的 postback(InstagramChatChannel.TnCFlow.IsTnCPayload 放行)。
|
||||||
|
每步记 Add-LoopStep。成功返回 $null,失败返回原因——收尾(下班、打印记录)各场景不同,交给调用方的 Fail:
|
||||||
|
$err = Complete-LoopTnC $igsid $TimeoutSec; if ($err) { Fail $err }
|
||||||
|
#>
|
||||||
|
# 默认选 English:没有条款流程时端用户语言就是英文,S5 的问句按钮(-YesTitle Yes)跟着模板语言走
|
||||||
|
function Complete-LoopTnC([string]$Igsid, [int]$TimeoutSec = 60, [string]$Language = 'en-us') {
|
||||||
|
$text = 'loop-tnc ' + [guid]::NewGuid().ToString('N').Substring(0, 6)
|
||||||
|
$e = Send-LoopIg $Igsid @{ kind = 'text'; text = $text }
|
||||||
|
if (-not $e) { return "条款前的第一句「$text」没送进 iXserver" }
|
||||||
|
# 条款第一条带 4 个外链,标题里认得出来
|
||||||
|
$tnc = Wait-LoopOutbound $Igsid $e.id { $_.summary -like '*Terms and Conditions*' } $TimeoutSec
|
||||||
|
if (-not $tnc) { return "$TimeoutSec 秒内没收到条款(T_and_C_Setting 没开?见 db\ixserver\loop_patch.sql 第 3 节;或 ABP 模板的 agreementMessages 为空)" }
|
||||||
|
Add-LoopStep '新用户第一句话被条款拦下' $true "$Igsid「$text」→ $($tnc.summary)"
|
||||||
|
|
||||||
|
Start-Sleep -Milliseconds 1500 # 条款组可能不止一条,等它发完再找 Agree 按钮
|
||||||
|
$agree = @(Get-LoopOutbound $Igsid $e.id | ForEach-Object { Get-LoopChoices $_ } | Where-Object { $_.payload -eq 'Agree' }) | Select-Object -First 1
|
||||||
|
$how = if ($agree) { "点模板里的「$($agree.title)」($($agree.kind))" } else { '条款里没有 Agree 按钮(ABP 没打 loop_patch 第 3 节?),发 postback Agree' }
|
||||||
|
if (-not $agree) { $agree = @{ kind = 'postback'; title = 'Agree'; payload = 'Agree' } }
|
||||||
|
$e = Send-LoopIg $Igsid @{ kind = $agree.kind; title = $agree.title; payload = $agree.payload; text = $agree.title }
|
||||||
|
if (-not $e) { return '同意没送进 iXserver' }
|
||||||
|
$switchPayload = "switch $Language"
|
||||||
|
$menu = Wait-LoopOutbound $Igsid $e.id { @(Get-LoopChoices $_ | Where-Object { $_.payload -eq $switchPayload }).Count } $TimeoutSec
|
||||||
|
if (-not $menu) { return "$TimeoutSec 秒内同意之后没收到带「$switchPayload」的选语言按钮" }
|
||||||
|
Add-LoopStep '同意条款,收到选语言' $true $how
|
||||||
|
|
||||||
|
$choice = @(Get-LoopChoices $menu | Where-Object { $_.payload -eq $switchPayload })[0]
|
||||||
|
$e = Send-LoopIg $Igsid @{ kind = $choice.kind; title = $choice.title; payload = $choice.payload; text = $choice.title }
|
||||||
|
if (-not $e) { return '选语言没送进 iXserver' }
|
||||||
|
$greeting = Wait-LoopOutbound $Igsid $e.id { $true } $TimeoutSec
|
||||||
|
if (-not $greeting) { return "$TimeoutSec 秒内选完语言没收到问候(条款模板第四组)" }
|
||||||
|
Add-LoopStep "选「$($choice.title)」,收到问候" $true $greeting.summary
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<#
|
<#
|
||||||
S1:IG 用户发一条文本 → iXserver → 第三方 chatbot(echo 自动回复)→ iXserver → IG 用户收到 echo。
|
S1:IG 用户发一条文本 → iXserver → 第三方 chatbot(echo 自动回复)→ iXserver → IG 用户收到 echo。
|
||||||
|
每次用新的 IGSID,先走完条款同意(_lib.ps1 的 Complete-LoopTnC)。
|
||||||
前提:.\loop.ps1 up 各角色都健康,chatbot 以 --auto-reply echo 运行(config\loop.psd1 的 ChatbotAutoReply)。
|
前提:.\loop.ps1 up 各角色都健康,chatbot 以 --auto-reply echo 运行(config\loop.psd1 的 ChatbotAutoReply)。
|
||||||
通过退出 0,失败退出 1 并打印两侧的记录;两种情况都在 run\reports\ 生成场景报告(观察台在跑时)。
|
通过退出 0,失败退出 1 并打印两侧的记录;两种情况都在 run\reports\ 生成场景报告(观察台在跑时)。
|
||||||
用法:.\scenarios\s1-ig-text-echo.ps1 [-TimeoutSec 60] [-NoReport]
|
用法:.\scenarios\s1-ig-text-echo.ps1 [-TimeoutSec 60] [-NoReport]
|
||||||
|
|
@ -27,8 +28,11 @@ function Fail([string]$why) {
|
||||||
|
|
||||||
Start-LoopRun 'S1' 'IG 文本 → 第三方 echo → IG'
|
Start-LoopRun 'S1' 'IG 文本 → 第三方 echo → IG'
|
||||||
Invoke-RestMethod "$ig/_api/clear" -Method Post | Out-Null
|
Invoke-RestMethod "$ig/_api/clear" -Method Post | Out-Null
|
||||||
|
$igsid = 'loop-s1-' + [guid]::NewGuid().ToString('N').Substring(0, 8)
|
||||||
|
$err = Complete-LoopTnC $igsid $TimeoutSec
|
||||||
|
if ($err) { Fail $err }
|
||||||
$text = 'loop-s1 ' + [guid]::NewGuid().ToString('N').Substring(0, 8)
|
$text = 'loop-s1 ' + [guid]::NewGuid().ToString('N').Substring(0, 8)
|
||||||
$sent = Invoke-RestMethod "$ig/_api/send" -Method Post -ContentType 'application/json; charset=utf-8' -Body (@{ kind = 'text'; text = $text } | ConvertTo-Json)
|
$sent = Invoke-RestMethod "$ig/_api/send" -Method Post -ContentType 'application/json; charset=utf-8' -Body ([Text.Encoding]::UTF8.GetBytes((@{ kind = 'text'; igsid = $igsid; text = $text } | ConvertTo-Json)))
|
||||||
if ($sent.event.response.status -ne 200) { Fail "iXserver 收 webhook 返回 $($sent.event.response.status)" }
|
if ($sent.event.response.status -ne 200) { Fail "iXserver 收 webhook 返回 $($sent.event.response.status)" }
|
||||||
Add-LoopStep 'iXserver 收下 webhook' $true "「$text」HTTP 200"
|
Add-LoopStep 'iXserver 收下 webhook' $true "「$text」HTTP 200"
|
||||||
|
|
||||||
|
|
@ -37,7 +41,7 @@ $expected = "echo: $text"
|
||||||
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
$deadline = (Get-Date).AddSeconds($TimeoutSec)
|
||||||
$hit = $null
|
$hit = $null
|
||||||
while (-not $hit -and (Get-Date) -lt $deadline) {
|
while (-not $hit -and (Get-Date) -lt $deadline) {
|
||||||
$hit = @((Invoke-RestMethod "$ig/_api/state").events | Where-Object { $_.kind -eq 'outbound' -and $_.summary -eq $expected }) | Select-Object -First 1
|
$hit = @((Invoke-RestMethod "$ig/_api/state").events | Where-Object { $_.kind -eq 'outbound' -and $_.igsid -eq $igsid -and $_.summary -eq $expected }) | Select-Object -First 1
|
||||||
if (-not $hit) { Start-Sleep -Milliseconds 500 }
|
if (-not $hit) { Start-Sleep -Milliseconds 500 }
|
||||||
}
|
}
|
||||||
if (-not $hit) { Fail "$TimeoutSec 秒内 iXserver 没有向 IG 用户发出「$expected」" }
|
if (-not $hit) { Fail "$TimeoutSec 秒内 iXserver 没有向 IG 用户发出「$expected」" }
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,9 @@ Invoke-RestMethod "$ig/_api/clear" -Method Post | Out-Null
|
||||||
|
|
||||||
# ---- 1. 服务号在机器人状态时建单:AI 状态只在建单那一刻定(方案 §2.3)
|
# ---- 1. 服务号在机器人状态时建单:AI 状态只在建单那一刻定(方案 §2.3)
|
||||||
Step 'SCRM 下班(服务号在机器人状态)' 'shift' @{ on = $false }
|
Step 'SCRM 下班(服务号在机器人状态)' 'shift' @{ on = $false }
|
||||||
|
# 条款同意那一句就建出了机器人工单,之后的 text1 落在同一张单上
|
||||||
|
$err = Complete-LoopTnC $igsid $TimeoutSec
|
||||||
|
if ($err) { Fail $err }
|
||||||
$text1 = 'S5 机器人阶段 ' + (New-Tag)
|
$text1 = 'S5 机器人阶段 ' + (New-Tag)
|
||||||
$e1 = Send-Ig @{ kind = 'text'; text = $text1 }
|
$e1 = Send-Ig @{ kind = 'text'; text = $text1 }
|
||||||
Add-LoopStep '新 IG 用户发消息' $true "$igsid「$text1」HTTP 200"
|
Add-LoopStep '新 IG 用户发消息' $true "$igsid「$text1」HTTP 200"
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,9 @@ Step 'SCRM 开工(切人工、在线 3)' 'shift' @{ on = $true } | Out-Null
|
||||||
Step '客服上线' 'agent-online' @{} | Out-Null
|
Step '客服上线' 'agent-online' @{} | Out-Null
|
||||||
|
|
||||||
$igsid = 'loop-s8-' + [guid]::NewGuid().ToString('N').Substring(0, 8)
|
$igsid = 'loop-s8-' + [guid]::NewGuid().ToString('N').Substring(0, 8)
|
||||||
|
# 人工状态下条款同样先拦第一句话;同意那一句就建出了人工工单,下面按 IGSID 认的还是这张
|
||||||
|
$err = Complete-LoopTnC $igsid $TimeoutSec
|
||||||
|
if ($err) { Fail $err }
|
||||||
$sent = Post "$ig/_api/send" @{ kind = 'text'; igsid = $igsid; text = 'S8 想找人工' }
|
$sent = Post "$ig/_api/send" @{ kind = 'text'; igsid = $igsid; text = 'S8 想找人工' }
|
||||||
if ($sent.event.response.status -ne 200) { Fail "iXserver 收 webhook 返回 $($sent.event.response.status)" }
|
if ($sent.event.response.status -ne 200) { Fail "iXserver 收 webhook 返回 $($sent.event.response.status)" }
|
||||||
Add-LoopStep '新 IG 用户发消息' $true "$igsid HTTP 200"
|
Add-LoopStep '新 IG 用户发消息' $true "$igsid HTTP 200"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue