| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869 |
- [CmdletBinding()]
- param(
- [string]$ConfigPath = (Join-Path $PSScriptRoot 'config\central-feedback.config.dpapi')
- )
- $ErrorActionPreference = 'Stop'
- . (Join-Path $PSScriptRoot 'Common.ps1')
- Add-Type -AssemblyName System.Net.Http
- function Invoke-FeishuJson {
- param([System.Net.Http.HttpClient]$Client, [string]$Uri, [object]$Body)
- $json = $Body | ConvertTo-Json -Compress -Depth 6
- $content = New-Object -TypeName System.Net.Http.StringContent -ArgumentList @($json, [Text.Encoding]::UTF8, 'application/json')
- try {
- $response = $Client.PostAsync($Uri, $content).GetAwaiter().GetResult()
- $raw = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
- }
- finally { $content.Dispose() }
- if (-not $response.IsSuccessStatusCode) { throw "Feishu HTTP $([int]$response.StatusCode)" }
- $data = $raw | ConvertFrom-Json
- if ($data.code -ne 0) { throw "Feishu code $($data.code)" }
- return $data
- }
- $config = Get-ProtectedConfig -Path $ConfigPath
- if (-not $config.app_id -or -not $config.app_secret -or -not $config.chat_id) { throw 'Server configuration is incomplete.' }
- $fileName = 'CENTRAL-SENDER-SELF-TEST.md'
- $testText = "# Central feedback sender self-test`r`n`r`nThis file verifies the server-to-Feishu delivery path. It contains no project data, credentials, or business information.`r`n"
- $fileBytes = [Text.Encoding]::UTF8.GetBytes($testText)
- $client = New-Object System.Net.Http.HttpClient
- try {
- $token = Invoke-FeishuJson -Client $client -Uri 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' -Body @{ app_id = [string]$config.app_id; app_secret = [string]$config.app_secret }
- $client.DefaultRequestHeaders.Authorization = New-Object -TypeName System.Net.Http.Headers.AuthenticationHeaderValue -ArgumentList @('Bearer', [string]$token.tenant_access_token)
- $form = New-Object System.Net.Http.MultipartFormDataContent
- try {
- $form.Add((New-Object -TypeName System.Net.Http.StringContent -ArgumentList 'stream'), 'file_type')
- $form.Add((New-Object -TypeName System.Net.Http.StringContent -ArgumentList @($fileName, [Text.Encoding]::UTF8)), 'file_name')
- $fileContent = New-Object -TypeName System.Net.Http.ByteArrayContent -ArgumentList (,$fileBytes)
- $fileContent.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse('application/octet-stream')
- $form.Add($fileContent, 'file', $fileName)
- $uploadResponse = $client.PostAsync('https://open.feishu.cn/open-apis/im/v1/files', $form).GetAwaiter().GetResult()
- $uploadRaw = $uploadResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult()
- }
- finally { $form.Dispose() }
- if (-not $uploadResponse.IsSuccessStatusCode) { throw "Feishu upload HTTP $([int]$uploadResponse.StatusCode)" }
- $upload = $uploadRaw | ConvertFrom-Json
- if ($upload.code -ne 0 -or [string]::IsNullOrWhiteSpace($upload.data.file_key)) { throw "Feishu upload code $($upload.code)" }
- $result = Invoke-FeishuJson -Client $client -Uri 'https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id' -Body @{
- receive_id = [string]$config.chat_id
- msg_type = 'file'
- content = (@{ file_key = [string]$upload.data.file_key; file_name = $fileName } | ConvertTo-Json -Compress)
- }
- Write-Output ([pscustomobject]@{
- status = 'sent'
- file_name = $fileName
- message_id = [string]$result.data.message_id
- sent_at = (Get-Date).ToString('o')
- })
- }
- finally {
- $client.Dispose()
- if ($fileBytes) { [Array]::Clear($fileBytes, 0, $fileBytes.Length) }
- $config.app_secret = $null
- }
|