Update Ikar server and Android client
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
param(
|
||||
[string]$BaseUrl = 'http://localhost:5099',
|
||||
[string]$AdminUsername = $env:Admin__Username,
|
||||
[string]$AdminPassword = $env:Admin__Password
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -AssemblyName System.Net.Http
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($AdminUsername) -or [string]::IsNullOrWhiteSpace($AdminPassword)) {
|
||||
throw 'Admin__Username and Admin__Password must be provided.'
|
||||
}
|
||||
|
||||
$stamp = Get-Date -Format 'yyyyMMddHHmmss'
|
||||
$actorUsername = "adminsmoke$stamp"
|
||||
$targetUsername = "adminpeer$stamp"
|
||||
$password = 'Demo123!'
|
||||
$attachmentName = "admin-attachment-$stamp.txt"
|
||||
$actorPhone = "+7911$stamp"
|
||||
$targetPhone = "+7922$stamp"
|
||||
|
||||
$actorSession = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/api/auth/register" `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
username = $actorUsername
|
||||
displayName = 'Admin Smoke Actor'
|
||||
password = $password
|
||||
phoneNumber = $actorPhone
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$targetSession = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/api/auth/register" `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
username = $targetUsername
|
||||
displayName = 'Admin Smoke Peer'
|
||||
password = $password
|
||||
phoneNumber = $targetPhone
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$actorHeaders = @{
|
||||
Authorization = "Bearer $($actorSession.accessToken)"
|
||||
}
|
||||
|
||||
$targetUser = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/api/users/search?q=$targetUsername" `
|
||||
-Headers $actorHeaders `
|
||||
-Method Get | Select-Object -First 1
|
||||
|
||||
$chat = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/api/chats/direct" `
|
||||
-Headers $actorHeaders `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
userId = $targetUser.id
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$tempFile = Join-Path $env:TEMP $attachmentName
|
||||
"admin attachment smoke $stamp" | Set-Content -Path $tempFile -NoNewline
|
||||
|
||||
$handler = New-Object System.Net.Http.HttpClientHandler
|
||||
$client = New-Object System.Net.Http.HttpClient($handler)
|
||||
$client.BaseAddress = [Uri]::new("$BaseUrl/")
|
||||
$client.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue('Bearer', $actorSession.accessToken)
|
||||
$form = New-Object System.Net.Http.MultipartFormDataContent
|
||||
$form.Add((New-Object System.Net.Http.StringContent('admin smoke attachment')), 'text')
|
||||
$fileStream = [System.IO.File]::OpenRead($tempFile)
|
||||
$fileContent = New-Object System.Net.Http.StreamContent($fileStream)
|
||||
$fileContent.Headers.ContentType = New-Object System.Net.Http.Headers.MediaTypeHeaderValue('text/plain')
|
||||
$form.Add($fileContent, 'file', [System.IO.Path]::GetFileName($tempFile))
|
||||
$uploadResponse = $client.PostAsync("api/chats/$($chat.id)/attachments", $form).GetAwaiter().GetResult()
|
||||
$uploadPayload = $uploadResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult()
|
||||
$fileStream.Dispose()
|
||||
$form.Dispose()
|
||||
$client.Dispose()
|
||||
$handler.Dispose()
|
||||
|
||||
if (-not $uploadResponse.IsSuccessStatusCode) {
|
||||
throw $uploadPayload
|
||||
}
|
||||
|
||||
$uploadedMessage = $uploadPayload | ConvertFrom-Json
|
||||
$attachment = $uploadedMessage.attachments[0]
|
||||
|
||||
$adminSession = New-Object Microsoft.PowerShell.Commands.WebRequestSession
|
||||
Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/session/login" `
|
||||
-WebSession $adminSession `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
username = $AdminUsername
|
||||
password = $AdminPassword
|
||||
} | ConvertTo-Json) | Out-Null
|
||||
|
||||
$status = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/status" `
|
||||
-WebSession $adminSession `
|
||||
-Method Get
|
||||
|
||||
$usersBeforeDeleteResponse = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/users?q=adminsmoke$stamp" `
|
||||
-WebSession $adminSession `
|
||||
-Method Get
|
||||
[object[]]$usersBeforeDelete = if ($null -eq $usersBeforeDeleteResponse) { @() } else { @($usersBeforeDeleteResponse) }
|
||||
|
||||
$chatsBeforeDeleteResponse = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/chats?q=adminsmoke$stamp" `
|
||||
-WebSession $adminSession `
|
||||
-Method Get
|
||||
[object[]]$chatsBeforeDelete = if ($null -eq $chatsBeforeDeleteResponse) { @() } else { @($chatsBeforeDeleteResponse) }
|
||||
|
||||
$attachmentsBeforeDeleteResponse = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/attachments?q=$attachmentName" `
|
||||
-WebSession $adminSession `
|
||||
-Method Get
|
||||
[object[]]$attachmentsBeforeDelete = if ($null -eq $attachmentsBeforeDeleteResponse) { @() } else { @($attachmentsBeforeDeleteResponse) }
|
||||
|
||||
$downloadPath = Join-Path $env:TEMP "admin-download-$stamp.txt"
|
||||
Invoke-WebRequest `
|
||||
-Uri "$BaseUrl/admin/api/attachments/$($attachment.id)/content" `
|
||||
-WebSession $adminSession `
|
||||
-OutFile $downloadPath | Out-Null
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/users/$($actorSession.user.id)/block" `
|
||||
-WebSession $adminSession `
|
||||
-Method Post | Out-Null
|
||||
|
||||
$blockedStatusCode = 0
|
||||
try {
|
||||
Invoke-WebRequest `
|
||||
-Uri "$BaseUrl/api/auth/login" `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
username = $actorUsername
|
||||
password = $password
|
||||
} | ConvertTo-Json) | Out-Null
|
||||
}
|
||||
catch {
|
||||
$blockedStatusCode = [int]$_.Exception.Response.StatusCode
|
||||
}
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/users/$($actorSession.user.id)/unblock" `
|
||||
-WebSession $adminSession `
|
||||
-Method Post | Out-Null
|
||||
|
||||
$actorSessionAfterUnblock = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/api/auth/login" `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
username = $actorUsername
|
||||
password = $password
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$actorHeadersAfterUnblock = @{
|
||||
Authorization = "Bearer $($actorSessionAfterUnblock.accessToken)"
|
||||
}
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/chats/$($chat.id)" `
|
||||
-WebSession $adminSession `
|
||||
-Method Delete | Out-Null
|
||||
|
||||
$chatDeleted = $false
|
||||
try {
|
||||
Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/api/chats/$($chat.id)" `
|
||||
-Headers $actorHeadersAfterUnblock `
|
||||
-Method Get | Out-Null
|
||||
}
|
||||
catch {
|
||||
$chatDeleted = [int]$_.Exception.Response.StatusCode -eq 404
|
||||
}
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/users/$($actorSession.user.id)" `
|
||||
-WebSession $adminSession `
|
||||
-Method Delete | Out-Null
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/users/$($targetSession.user.id)" `
|
||||
-WebSession $adminSession `
|
||||
-Method Delete | Out-Null
|
||||
|
||||
$usersAfterDeleteResponse = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/users?q=adminsmoke$stamp" `
|
||||
-WebSession $adminSession `
|
||||
-Method Get
|
||||
[object[]]$usersAfterDelete = if ($null -eq $usersAfterDeleteResponse) { @() } else { @($usersAfterDeleteResponse) }
|
||||
|
||||
Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/admin/api/session/logout" `
|
||||
-WebSession $adminSession `
|
||||
-Method Post | Out-Null
|
||||
|
||||
$logoutBlocked = $false
|
||||
try {
|
||||
Invoke-WebRequest `
|
||||
-Uri "$BaseUrl/admin/api/status" `
|
||||
-WebSession $adminSession `
|
||||
-Method Get | Out-Null
|
||||
}
|
||||
catch {
|
||||
$logoutBlocked = [int]$_.Exception.Response.StatusCode -eq 401
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
ActorUsername = $actorUsername
|
||||
TargetUsername = $targetUsername
|
||||
TotalUsers = $status.totalUsers
|
||||
DatabaseReachable = $status.databaseReachable
|
||||
UsersBeforeCount = $usersBeforeDelete.Count
|
||||
ChatsBeforeCount = $chatsBeforeDelete.Count
|
||||
AttachmentsBeforeCount = $attachmentsBeforeDelete.Count
|
||||
UsersListed = $usersBeforeDelete.Count -ge 1
|
||||
ChatsListed = $chatsBeforeDelete.Count -ge 1
|
||||
AttachmentsListed = $attachmentsBeforeDelete.Count -ge 1
|
||||
AttachmentDownloaded = (Get-Item $downloadPath).Length -gt 0
|
||||
BlockedLoginStatus = $blockedStatusCode
|
||||
ChatDeleted = $chatDeleted
|
||||
UsersDeleted = $usersAfterDelete.Count -eq 0
|
||||
AdminLogoutBlocked = $logoutBlocked
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
param(
|
||||
[string]$BaseUrl = 'http://localhost:5099'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$stamp = Get-Date -Format 'yyyyMMddHHmmss'
|
||||
$phoneA = "+7999$stamp"
|
||||
$phoneB = "+7888$stamp"
|
||||
|
||||
function Invoke-JsonPost {
|
||||
param(
|
||||
[string]$Uri,
|
||||
[hashtable]$Body,
|
||||
[hashtable]$Headers = @{}
|
||||
)
|
||||
|
||||
return Invoke-RestMethod `
|
||||
-Uri $Uri `
|
||||
-Method Post `
|
||||
-Headers $Headers `
|
||||
-ContentType 'application/json' `
|
||||
-Body ($Body | ConvertTo-Json)
|
||||
}
|
||||
|
||||
$challengeA = Invoke-JsonPost `
|
||||
-Uri "$BaseUrl/api/auth/request-code" `
|
||||
-Body @{ phoneNumber = $phoneA }
|
||||
|
||||
$sessionA = Invoke-JsonPost `
|
||||
-Uri "$BaseUrl/api/auth/verify-code" `
|
||||
-Body @{
|
||||
challengeId = $challengeA.challengeId
|
||||
phoneNumber = $phoneA
|
||||
code = $challengeA.testCode
|
||||
displayName = 'Phone Smoke A'
|
||||
}
|
||||
|
||||
$challengeB = Invoke-JsonPost `
|
||||
-Uri "$BaseUrl/api/auth/request-code" `
|
||||
-Body @{ phoneNumber = $phoneB }
|
||||
|
||||
$sessionB = Invoke-JsonPost `
|
||||
-Uri "$BaseUrl/api/auth/verify-code" `
|
||||
-Body @{
|
||||
challengeId = $challengeB.challengeId
|
||||
phoneNumber = $phoneB
|
||||
code = $challengeB.testCode
|
||||
displayName = 'Phone Smoke B'
|
||||
}
|
||||
|
||||
$headersA = @{
|
||||
Authorization = "Bearer $($sessionA.accessToken)"
|
||||
}
|
||||
|
||||
$meA = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/api/users/me" `
|
||||
-Method Get `
|
||||
-Headers $headersA
|
||||
|
||||
$resolve = Invoke-JsonPost `
|
||||
-Uri "$BaseUrl/api/users/contacts/resolve" `
|
||||
-Headers $headersA `
|
||||
-Body @{
|
||||
phoneNumbers = @(
|
||||
$phoneB,
|
||||
'+7 (777) 000-00-00'
|
||||
)
|
||||
}
|
||||
|
||||
if ($null -eq $resolve) {
|
||||
$resolve = @()
|
||||
}
|
||||
else {
|
||||
$resolve = @($resolve)
|
||||
}
|
||||
|
||||
$repeatChallengeA = Invoke-JsonPost `
|
||||
-Uri "$BaseUrl/api/auth/request-code" `
|
||||
-Body @{ phoneNumber = $phoneA }
|
||||
|
||||
$repeatSessionA = Invoke-JsonPost `
|
||||
-Uri "$BaseUrl/api/auth/verify-code" `
|
||||
-Body @{
|
||||
challengeId = $repeatChallengeA.challengeId
|
||||
phoneNumber = $phoneA
|
||||
code = $repeatChallengeA.testCode
|
||||
displayName = $null
|
||||
}
|
||||
|
||||
$chat = Invoke-JsonPost `
|
||||
-Uri "$BaseUrl/api/chats/direct" `
|
||||
-Headers $headersA `
|
||||
-Body @{
|
||||
userId = $sessionB.user.id
|
||||
}
|
||||
|
||||
$chats = Invoke-RestMethod `
|
||||
-Uri "$BaseUrl/api/chats" `
|
||||
-Method Get `
|
||||
-Headers $headersA
|
||||
|
||||
if ($null -eq $chats) {
|
||||
$chats = @()
|
||||
}
|
||||
else {
|
||||
$chats = @($chats)
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
PhoneARegistered = -not $challengeA.isRegistered
|
||||
PhoneBRegistered = -not $challengeB.isRegistered
|
||||
PhoneAMeDisplayName = $meA.displayName
|
||||
PhoneAMePhone = $meA.phoneNumber
|
||||
ResolvedCount = $resolve.Count
|
||||
ResolvedPhone = if ($resolve.Count -gt 0) { $resolve[0].phoneNumber } else { $null }
|
||||
ResolvedDisplayName = if ($resolve.Count -gt 0) { $resolve[0].user.displayName } else { $null }
|
||||
ReLoginRegistered = $repeatChallengeA.isRegistered
|
||||
ReLoginSameUser = $repeatSessionA.user.id -eq $sessionA.user.id
|
||||
DirectChatType = $chat.type
|
||||
DirectChatCreated = $chat.type -eq 1 -or $chat.type -eq 'Direct'
|
||||
ChatsCount = $chats.Count
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
param(
|
||||
[string]$Slug = 'ikar-android',
|
||||
[string]$Name,
|
||||
[string]$Summary = 'Android client build',
|
||||
[string]$Description = 'Android release channel for the Ikar client.',
|
||||
[string]$Version,
|
||||
[string]$Channel = 'stable',
|
||||
[string]$Notes,
|
||||
[string]$RepositoryUrl,
|
||||
[string]$HomepageUrl = 'https://ikar.kusoft.xyz',
|
||||
[switch]$Unlisted,
|
||||
[switch]$SkipBuild
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Require-Tool([string]$Path, [string]$Label) {
|
||||
if (-not (Test-Path $Path)) {
|
||||
throw "$Label was not found: $Path"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ApkBadgingValue([string]$BadgingText, [string]$Pattern) {
|
||||
$match = [regex]::Match($BadgingText, $Pattern, [System.Text.RegularExpressions.RegexOptions]::Multiline)
|
||||
if (-not $match.Success) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $match.Groups[1].Value
|
||||
}
|
||||
|
||||
function Resolve-ArgusPlatform([string[]]$NativeCodes) {
|
||||
$normalized = $NativeCodes | Where-Object { $_ } | ForEach-Object { $_.Trim().ToLowerInvariant() }
|
||||
$allUniversalAbis = @('arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64')
|
||||
if (@($allUniversalAbis | Where-Object { $normalized -contains $_ }).Count -eq $allUniversalAbis.Count) {
|
||||
return 'android-universal'
|
||||
}
|
||||
|
||||
if ($normalized -contains 'arm64-v8a') {
|
||||
return 'android-arm64'
|
||||
}
|
||||
|
||||
if ($normalized -contains 'armeabi-v7a') {
|
||||
return 'android-armv7'
|
||||
}
|
||||
|
||||
if ($normalized -contains 'x86_64') {
|
||||
return 'android-x86_64'
|
||||
}
|
||||
|
||||
if ($normalized -contains 'x86') {
|
||||
return 'android-x86'
|
||||
}
|
||||
|
||||
return 'android'
|
||||
}
|
||||
|
||||
function Get-RepositoryUrl() {
|
||||
$remote = git remote get-url origin 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($remote)) {
|
||||
return $remote.Trim()
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
$repoRoot = Split-Path -Parent $PSScriptRoot
|
||||
Set-Location $repoRoot
|
||||
|
||||
$javaHome = if ($env:JAVA_HOME) { $env:JAVA_HOME } else { 'C:\Program Files\Android\openjdk\jdk-21.0.8' }
|
||||
$androidHome = if ($env:ANDROID_HOME) { $env:ANDROID_HOME } else { 'C:\Users\seven\AppData\Local\Android\Sdk' }
|
||||
$env:JAVA_HOME = $javaHome
|
||||
$env:ANDROID_HOME = $androidHome
|
||||
|
||||
$projectPath = Join-Path $repoRoot 'src\Ikar.Client\Ikar.Client.csproj'
|
||||
$publishDir = Join-Path $repoRoot 'src\Ikar.Client\bin\Release\net10.0-android'
|
||||
$releaseApkPath = Join-Path $publishDir 'com.seven.ikar-Signed.apk'
|
||||
$aaptPath = Join-Path $androidHome 'build-tools\36.1.0\aapt.exe'
|
||||
$apksignerPath = Join-Path $androidHome 'build-tools\36.1.0\apksigner.bat'
|
||||
|
||||
Require-Tool $projectPath 'Ikar.Client project'
|
||||
Require-Tool $aaptPath 'aapt'
|
||||
Require-Tool $apksignerPath 'apksigner'
|
||||
|
||||
if (-not $SkipBuild) {
|
||||
dotnet publish $projectPath -f net10.0-android -c Release -p:AndroidPackageFormat=apk -v minimal --no-restore
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "dotnet publish failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $releaseApkPath)) {
|
||||
throw "Release APK was not found: $releaseApkPath"
|
||||
}
|
||||
|
||||
$badging = & $aaptPath dump badging $releaseApkPath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "aapt badging failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
$badgingText = ($badging | Out-String)
|
||||
$packageName = Get-ApkBadgingValue $badgingText "package: name='([^']+)'"
|
||||
$versionCode = Get-ApkBadgingValue $badgingText "versionCode='([^']+)'"
|
||||
$versionName = Get-ApkBadgingValue $badgingText "versionName='([^']+)'"
|
||||
$targetSdk = Get-ApkBadgingValue $badgingText "targetSdkVersion:'([^']+)'"
|
||||
$applicationLabel = Get-ApkBadgingValue $badgingText "application-label:'([^']+)'"
|
||||
$nativeCodeLine = ($badging | Where-Object { $_ -like 'native-code:*' } | Select-Object -First 1)
|
||||
$nativeCodes = if ($nativeCodeLine) {
|
||||
[regex]::Matches($nativeCodeLine, "'([^']+)'") | ForEach-Object { $_.Groups[1].Value }
|
||||
} else {
|
||||
@()
|
||||
}
|
||||
|
||||
if (-not $packageName) {
|
||||
throw 'Package name could not be extracted from APK.'
|
||||
}
|
||||
|
||||
if (-not $versionCode) {
|
||||
throw 'Version code could not be extracted from APK.'
|
||||
}
|
||||
|
||||
if (-not $versionName) {
|
||||
throw 'Version name could not be extracted from APK.'
|
||||
}
|
||||
|
||||
$signatureOutput = & $apksignerPath verify --print-certs $releaseApkPath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "apksigner verification failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
$signatureText = ($signatureOutput | Out-String)
|
||||
$signerDn = Get-ApkBadgingValue $signatureText "Signer #1 certificate DN: (.+)"
|
||||
$signerSha256 = Get-ApkBadgingValue $signatureText "Signer #1 certificate SHA-256 digest: ([A-Fa-f0-9]+)"
|
||||
if ($signerDn) {
|
||||
$signerDn = $signerDn.Trim()
|
||||
}
|
||||
if ($signerSha256) {
|
||||
$signerSha256 = $signerSha256.Trim()
|
||||
}
|
||||
$isDebugSigned = $false
|
||||
if ($signerDn -and $signerDn -like '*CN=Android Debug*') {
|
||||
$isDebugSigned = $true
|
||||
}
|
||||
|
||||
$resolvedName = if ($Name) { $Name } elseif ($applicationLabel) { $applicationLabel } else { 'Ikar' }
|
||||
$resolvedVersion = if ($Version) { $Version } else { '{0}+{1}' -f $versionName, $versionCode }
|
||||
$resolvedRepositoryUrl = if ($RepositoryUrl) { $RepositoryUrl } else { Get-RepositoryUrl }
|
||||
$resolvedPlatform = Resolve-ArgusPlatform $nativeCodes
|
||||
$apkItem = Get-Item $releaseApkPath
|
||||
$apkHash = (Get-FileHash -Path $releaseApkPath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
|
||||
$stageDirectory = Join-Path $repoRoot ("artifacts\argus\{0}\v{1}" -f $Slug, $versionCode)
|
||||
if (-not (Test-Path $stageDirectory)) {
|
||||
New-Item -ItemType Directory -Path $stageDirectory -Force | Out-Null
|
||||
}
|
||||
|
||||
$stagedApkName = "{0}-{1}.apk" -f $Slug, $versionCode
|
||||
$stagedApkPath = Join-Path $stageDirectory $stagedApkName
|
||||
Copy-Item $releaseApkPath $stagedApkPath -Force
|
||||
|
||||
$metadata = [ordered]@{
|
||||
slug = $Slug
|
||||
name = $resolvedName
|
||||
summary = $Summary
|
||||
description = $Description
|
||||
version = $resolvedVersion
|
||||
channel = $Channel
|
||||
platform = $resolvedPlatform
|
||||
packageKind = 'apk'
|
||||
notes = if ($Notes) { $Notes } else { $null }
|
||||
repositoryUrl = if ($resolvedRepositoryUrl) { $resolvedRepositoryUrl } else { $null }
|
||||
homepageUrl = if ($HomepageUrl) { $HomepageUrl } else { $null }
|
||||
androidPackageName = $packageName
|
||||
androidVersionCode = [int]$versionCode
|
||||
targetSdkVersion = if ($targetSdk) { [int]$targetSdk } else { $null }
|
||||
isListed = (-not $Unlisted)
|
||||
packagePath = $stagedApkPath
|
||||
packageFileName = $stagedApkName
|
||||
packageSizeBytes = $apkItem.Length
|
||||
packageSha256 = $apkHash
|
||||
preparedAt = [DateTimeOffset]::UtcNow.ToString('O')
|
||||
verifiedFromApk = [ordered]@{
|
||||
sourceApk = $releaseApkPath
|
||||
applicationLabel = if ($applicationLabel) { $applicationLabel } else { $null }
|
||||
packageName = $packageName
|
||||
versionCode = [int]$versionCode
|
||||
versionName = $versionName
|
||||
targetSdkVersion = if ($targetSdk) { [int]$targetSdk } else { $null }
|
||||
nativeCodes = $nativeCodes
|
||||
signerDn = if ($signerDn) { $signerDn } else { $null }
|
||||
signerSha256 = if ($signerSha256) { $signerSha256.ToLowerInvariant() } else { $null }
|
||||
isDebugSigned = $isDebugSigned
|
||||
}
|
||||
}
|
||||
|
||||
$metadataPath = Join-Path $stageDirectory 'argus-package.json'
|
||||
$metadata | ConvertTo-Json -Depth 6 | Set-Content -Path $metadataPath -Encoding UTF8
|
||||
|
||||
$summaryLines = @(
|
||||
"slug=$Slug"
|
||||
"name=$resolvedName"
|
||||
"version=$resolvedVersion"
|
||||
"channel=$Channel"
|
||||
"platform=$resolvedPlatform"
|
||||
"packageKind=apk"
|
||||
"androidPackageName=$packageName"
|
||||
"androidVersionCode=$versionCode"
|
||||
"targetSdkVersion=$targetSdk"
|
||||
"packagePath=$stagedApkPath"
|
||||
"packageSizeBytes=$($apkItem.Length)"
|
||||
"packageSha256=$apkHash"
|
||||
"signerDn=$signerDn"
|
||||
"signerSha256=$($signerSha256.ToLowerInvariant())"
|
||||
"isDebugSigned=$isDebugSigned"
|
||||
)
|
||||
$summaryPath = Join-Path $stageDirectory 'argus-package-summary.txt'
|
||||
$summaryLines | Set-Content -Path $summaryPath -Encoding UTF8
|
||||
|
||||
Write-Output "Prepared Argus package files."
|
||||
Write-Output "APK: $stagedApkPath"
|
||||
Write-Output "Metadata: $metadataPath"
|
||||
Write-Output "Summary: $summaryPath"
|
||||
@@ -0,0 +1,10 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-Location (Join-Path $PSScriptRoot '..')
|
||||
$publishDir = '.\src\Ikar.Client\bin\Release\net10.0-android\publish'
|
||||
|
||||
if (Test-Path $publishDir) {
|
||||
Remove-Item $publishDir -Recurse -Force
|
||||
}
|
||||
|
||||
dotnet publish .\src\Ikar.Client\Ikar.Client.csproj -f net10.0-android -c Release -p:AndroidPackageFormat=apk
|
||||
Get-ChildItem $publishDir | Where-Object { $_.Name -like 'com.seven.massenger*' } | Remove-Item -Force
|
||||
@@ -0,0 +1,298 @@
|
||||
param(
|
||||
[string]$ServerHost = '192.168.0.185',
|
||||
[string]$Username = 'sevenhill',
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Password,
|
||||
[string]$ArgusBaseUrl = 'https://argus.kusoft.xyz',
|
||||
[string]$ArgusInternalBaseUrl = 'http://127.0.0.1:5105',
|
||||
[string]$ArgusEnvPath = '/home/sevenhill/argus-deploy/.env.server',
|
||||
[string]$Slug = 'ikar-android',
|
||||
[string]$Notes = 'Android client update for Ikar',
|
||||
[string]$AppName = 'Ikar Android',
|
||||
[string]$Summary = 'Android client build',
|
||||
[string]$Description = 'Android release channel for the Ikar client.',
|
||||
[string]$HomepageUrl = 'https://ikar.kusoft.xyz',
|
||||
[string]$Platform = 'android',
|
||||
[string]$PackageKind = 'apk',
|
||||
[string]$Channel = 'stable'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Set-Location (Join-Path $PSScriptRoot '..')
|
||||
|
||||
$repoRoot = (Get-Location).Path
|
||||
$projectPath = '.\src\Ikar.Client\Ikar.Client.csproj'
|
||||
|
||||
[xml]$projectXml = Get-Content $projectPath
|
||||
$versionName = $projectXml.Project.PropertyGroup.ApplicationDisplayVersion | Select-Object -First 1
|
||||
$versionCode = [int]($projectXml.Project.PropertyGroup.ApplicationVersion | Select-Object -First 1)
|
||||
$publishDir = Join-Path $repoRoot (".codex-temp\android-update-publish\v{0}" -f $versionCode)
|
||||
|
||||
if (Test-Path $publishDir) {
|
||||
Remove-Item $publishDir -Recurse -Force
|
||||
}
|
||||
|
||||
dotnet publish $projectPath -f net10.0-android -c Release -p:AndroidPackageFormat=apk -p:UseSharedCompilation=false -o $publishDir
|
||||
|
||||
$sourceApk = Join-Path $publishDir 'com.seven.ikar-Signed.apk'
|
||||
if (-not (Test-Path $sourceApk)) {
|
||||
throw "Signed APK not found: $sourceApk"
|
||||
}
|
||||
|
||||
$remoteFileName = "ikar-android-$versionCode.apk"
|
||||
$localCopy = Join-Path $publishDir $remoteFileName
|
||||
Copy-Item $sourceApk $localCopy -Force
|
||||
|
||||
$manifest = [ordered]@{
|
||||
versionCode = $versionCode
|
||||
versionName = $versionName
|
||||
packageFileName = $remoteFileName
|
||||
publishedAt = [DateTimeOffset]::UtcNow.ToString('O')
|
||||
notes = $Notes
|
||||
}
|
||||
|
||||
$manifestPath = Join-Path $publishDir 'latest.json'
|
||||
$manifest | ConvertTo-Json -Depth 4 | Set-Content $manifestPath -Encoding UTF8
|
||||
|
||||
$argusPublishResult = @"
|
||||
import json
|
||||
import os
|
||||
import paramiko
|
||||
import posixpath
|
||||
|
||||
host = os.environ['IKAR_UPDATE_HOST']
|
||||
username = os.environ['IKAR_UPDATE_USERNAME']
|
||||
password = os.environ['IKAR_UPDATE_PASSWORD']
|
||||
env_path = os.environ['IKAR_UPDATE_ARGUS_ENV_PATH']
|
||||
local_apk_path = os.environ['IKAR_UPDATE_APK_PATH']
|
||||
remote_apk_name = os.environ['IKAR_UPDATE_REMOTE_APK_NAME']
|
||||
argus_internal_base_url = os.environ['IKAR_UPDATE_ARGUS_INTERNAL_BASE_URL']
|
||||
slug = os.environ['IKAR_UPDATE_ARGUS_SLUG']
|
||||
app_name = os.environ['IKAR_UPDATE_ARGUS_NAME']
|
||||
summary = os.environ['IKAR_UPDATE_ARGUS_SUMMARY']
|
||||
description = os.environ['IKAR_UPDATE_ARGUS_DESCRIPTION']
|
||||
homepage_url = os.environ['IKAR_UPDATE_ARGUS_HOMEPAGE_URL']
|
||||
release_version = os.environ['IKAR_UPDATE_ARGUS_VERSION']
|
||||
channel = os.environ['IKAR_UPDATE_ARGUS_CHANNEL']
|
||||
platform = os.environ['IKAR_UPDATE_ARGUS_PLATFORM']
|
||||
package_kind = os.environ['IKAR_UPDATE_ARGUS_PACKAGE_KIND']
|
||||
notes = os.environ['IKAR_UPDATE_ARGUS_NOTES']
|
||||
temp_root = f"/home/{username}/.argus-upload"
|
||||
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
client.connect(host, username=username, password=password, timeout=10)
|
||||
sftp = client.open_sftp()
|
||||
|
||||
with sftp.open(env_path, 'r') as handle:
|
||||
content = handle.read().decode('utf-8', errors='replace')
|
||||
|
||||
values = {}
|
||||
for raw_line in content.splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith('#') or '=' not in line:
|
||||
continue
|
||||
key, value = line.split('=', 1)
|
||||
values[key.strip()] = value.strip()
|
||||
|
||||
username = values.get('Admin__Username')
|
||||
password = values.get('Admin__Password')
|
||||
if not username or not password:
|
||||
raise RuntimeError(f"Admin credentials were not found in {env_path}.")
|
||||
|
||||
parts = temp_root.strip('/').split('/')
|
||||
current = '/'
|
||||
for part in parts:
|
||||
current = posixpath.join(current, part)
|
||||
try:
|
||||
sftp.stat(current)
|
||||
except FileNotFoundError:
|
||||
sftp.mkdir(current)
|
||||
|
||||
remote_apk_path = posixpath.join(temp_root, remote_apk_name)
|
||||
sftp.put(local_apk_path, remote_apk_path)
|
||||
sftp.close()
|
||||
|
||||
def run_remote_python(script: str) -> str:
|
||||
stdin, stdout, stderr = client.exec_command("python3 -", timeout=600)
|
||||
stdin.write(script)
|
||||
stdin.channel.shutdown_write()
|
||||
out = stdout.read().decode('utf-8', errors='replace')
|
||||
err = stderr.read().decode('utf-8', errors='replace')
|
||||
exit_status = stdout.channel.recv_exit_status()
|
||||
if exit_status != 0:
|
||||
raise RuntimeError(f"Remote publish failed with exit status {exit_status}\\nSTDOUT:\\n{out}\\nSTDERR:\\n{err}")
|
||||
return out
|
||||
|
||||
remote_payload = json.dumps({
|
||||
'base_url': argus_internal_base_url.rstrip('/'),
|
||||
'slug': slug,
|
||||
'package_path': remote_apk_path,
|
||||
'admin_username': username,
|
||||
'admin_password': password,
|
||||
'app_name': app_name,
|
||||
'summary': summary,
|
||||
'description': description,
|
||||
'homepage_url': homepage_url,
|
||||
'release_version': release_version,
|
||||
'release_channel': channel,
|
||||
'release_platform': platform,
|
||||
'release_package_kind': package_kind,
|
||||
'release_notes': notes,
|
||||
})
|
||||
|
||||
remote_script = '''
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
import uuid
|
||||
|
||||
payload = json.loads(%s)
|
||||
base_url = payload['base_url']
|
||||
slug = payload['slug']
|
||||
package_path = payload['package_path']
|
||||
admin_username = payload['admin_username']
|
||||
admin_password = payload['admin_password']
|
||||
app_name = payload['app_name']
|
||||
summary = payload['summary']
|
||||
description = payload['description']
|
||||
homepage_url = payload['homepage_url']
|
||||
release_version = payload['release_version']
|
||||
release_channel = payload['release_channel']
|
||||
release_platform = payload['release_platform']
|
||||
release_package_kind = payload['release_package_kind']
|
||||
release_notes = payload['release_notes']
|
||||
|
||||
def read_response(response):
|
||||
body = response.read().decode('utf-8', errors='replace')
|
||||
return response.getcode(), body
|
||||
|
||||
opener = urllib.request.build_opener()
|
||||
|
||||
login_payload = json.dumps({
|
||||
'username': admin_username,
|
||||
'password': admin_password,
|
||||
}).encode('utf-8')
|
||||
login_request = urllib.request.Request(
|
||||
f"{base_url}/api/admin/session/login",
|
||||
data=login_payload,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
method='POST')
|
||||
login_response = opener.open(login_request, timeout=600)
|
||||
login_status, login_body = read_response(login_response)
|
||||
if login_status < 200 or login_status >= 300:
|
||||
raise RuntimeError(f"Login failed with status {login_status}: {login_body}")
|
||||
|
||||
cookie_header = login_response.headers.get('Set-Cookie', '')
|
||||
auth_cookie = cookie_header.split(';', 1)[0].strip()
|
||||
if not auth_cookie:
|
||||
raise RuntimeError('Argus login did not return an auth cookie.')
|
||||
|
||||
metadata_payload = json.dumps({
|
||||
'name': app_name,
|
||||
'summary': summary,
|
||||
'description': description,
|
||||
'homepageUrl': homepage_url or None,
|
||||
'repositoryUrl': None,
|
||||
'isListed': True,
|
||||
}).encode('utf-8')
|
||||
metadata_request = urllib.request.Request(
|
||||
f"{base_url}/api/admin/apps/{slug}",
|
||||
data=metadata_payload,
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': auth_cookie,
|
||||
},
|
||||
method='PUT')
|
||||
metadata_response = opener.open(metadata_request, timeout=600)
|
||||
metadata_status, metadata_body = read_response(metadata_response)
|
||||
if metadata_status < 200 or metadata_status >= 300:
|
||||
raise RuntimeError(f"Metadata update failed with status {metadata_status}: {metadata_body}")
|
||||
|
||||
with open(package_path, 'rb') as handle:
|
||||
file_bytes = handle.read()
|
||||
|
||||
boundary = f"----IkarArgusBoundary{uuid.uuid4().hex}"
|
||||
parts: list[bytes] = []
|
||||
|
||||
def add_field(name: str, value: str):
|
||||
parts.append(f"--{boundary}\\r\\n".encode('utf-8'))
|
||||
parts.append(f'Content-Disposition: form-data; name="{name}"\\r\\n\\r\\n'.encode('utf-8'))
|
||||
parts.append(value.encode('utf-8'))
|
||||
parts.append(b"\\r\\n")
|
||||
|
||||
for field_name, field_value in [
|
||||
('Version', release_version),
|
||||
('Channel', release_channel),
|
||||
('Platform', release_platform),
|
||||
('PackageKind', release_package_kind),
|
||||
]:
|
||||
add_field(field_name, field_value)
|
||||
|
||||
if release_notes:
|
||||
add_field('Notes', release_notes)
|
||||
|
||||
file_name = os.path.basename(package_path)
|
||||
parts.append(f"--{boundary}\\r\\n".encode('utf-8'))
|
||||
parts.append(
|
||||
f'Content-Disposition: form-data; name="Package"; filename="{file_name}"\\r\\n'.encode('utf-8'))
|
||||
parts.append(b"Content-Type: application/vnd.android.package-archive\\r\\n\\r\\n")
|
||||
parts.append(file_bytes)
|
||||
parts.append(b"\\r\\n")
|
||||
parts.append(f"--{boundary}--\\r\\n".encode('utf-8'))
|
||||
|
||||
release_request = urllib.request.Request(
|
||||
f"{base_url}/api/admin/apps/{slug}/releases",
|
||||
data=b''.join(parts),
|
||||
headers={
|
||||
'Content-Type': f'multipart/form-data; boundary={boundary}',
|
||||
'Cookie': auth_cookie,
|
||||
},
|
||||
method='POST')
|
||||
release_response = opener.open(release_request, timeout=600)
|
||||
release_status, release_body = read_response(release_response)
|
||||
if release_status < 200 or release_status >= 300:
|
||||
raise RuntimeError(f"Release upload failed with status {release_status}: {release_body}")
|
||||
|
||||
print(release_body)
|
||||
''' % json.dumps(remote_payload)
|
||||
|
||||
publish_output = run_remote_python(remote_script)
|
||||
|
||||
try:
|
||||
sftp = client.open_sftp()
|
||||
sftp.remove(remote_apk_path)
|
||||
sftp.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
client.close()
|
||||
print(json.dumps({'status': 'UPLOAD_OK', 'release': publish_output.strip()}))
|
||||
"@ | ForEach-Object {
|
||||
$env:IKAR_UPDATE_HOST = $ServerHost
|
||||
$env:IKAR_UPDATE_USERNAME = $Username
|
||||
$env:IKAR_UPDATE_PASSWORD = $Password
|
||||
$env:IKAR_UPDATE_ARGUS_ENV_PATH = $ArgusEnvPath
|
||||
$env:IKAR_UPDATE_APK_PATH = (Resolve-Path $localCopy)
|
||||
$env:IKAR_UPDATE_REMOTE_APK_NAME = $remoteFileName
|
||||
$env:IKAR_UPDATE_ARGUS_INTERNAL_BASE_URL = $ArgusInternalBaseUrl
|
||||
$env:IKAR_UPDATE_ARGUS_SLUG = $Slug
|
||||
$env:IKAR_UPDATE_ARGUS_NAME = $AppName
|
||||
$env:IKAR_UPDATE_ARGUS_SUMMARY = $Summary
|
||||
$env:IKAR_UPDATE_ARGUS_DESCRIPTION = $Description
|
||||
$env:IKAR_UPDATE_ARGUS_HOMEPAGE_URL = $HomepageUrl
|
||||
$env:IKAR_UPDATE_ARGUS_VERSION = "$versionName+$versionCode"
|
||||
$env:IKAR_UPDATE_ARGUS_CHANNEL = $Channel
|
||||
$env:IKAR_UPDATE_ARGUS_PLATFORM = $Platform
|
||||
$env:IKAR_UPDATE_ARGUS_PACKAGE_KIND = $PackageKind
|
||||
$env:IKAR_UPDATE_ARGUS_NOTES = $Notes
|
||||
$_
|
||||
} | python -
|
||||
|
||||
$publishResult = $argusPublishResult | ConvertFrom-Json
|
||||
if ($publishResult.status -ne 'UPLOAD_OK') {
|
||||
throw "Argus publish failed."
|
||||
}
|
||||
|
||||
Write-Output $publishResult.status
|
||||
@@ -0,0 +1,15 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-Location (Join-Path $PSScriptRoot '..')
|
||||
$project = '.\src\Ikar.WinUI\Ikar.WinUI.csproj'
|
||||
$target = '.\artifacts\winui-win64'
|
||||
|
||||
dotnet build $project -c Release -f net10.0-windows10.0.19041.0 -p:Platform=x64
|
||||
|
||||
if (Test-Path $target) {
|
||||
Remove-Item $target -Recurse -Force
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Path $target | Out-Null
|
||||
Copy-Item '.\src\Ikar.WinUI\bin\x64\Release\net10.0-windows10.0.19041.0\win-x64\*' $target -Recurse -Force
|
||||
Get-ChildItem $target -Recurse | Where-Object { $_.Name -like 'Massenger*' } | Remove-Item -Recurse -Force
|
||||
Write-Host "Windows client prepared at $target\Ikar.WinUI.exe"
|
||||
@@ -0,0 +1,3 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-Location (Join-Path $PSScriptRoot '..')
|
||||
dotnet run --project .\src\Ikar.Server\Ikar.Server.csproj
|
||||
@@ -0,0 +1,190 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -AssemblyName System.Net.Http
|
||||
|
||||
$baseUrl = 'http://localhost:5099'
|
||||
$stamp = Get-Date -Format 'yyyyMMddHHmmss'
|
||||
$username = "smoke$stamp"
|
||||
$password = 'Demo123!'
|
||||
$phoneNumber = "+7900$stamp"
|
||||
|
||||
$session = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/auth/register" `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
username = $username
|
||||
displayName = 'Smoke Test'
|
||||
password = $password
|
||||
phoneNumber = $phoneNumber
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$refreshed = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/auth/refresh" `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
refreshToken = $session.refreshToken
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$headers = @{
|
||||
Authorization = "Bearer $($refreshed.accessToken)"
|
||||
}
|
||||
|
||||
$installationId = "push-$stamp"
|
||||
$pushDevice = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/push/devices" `
|
||||
-Headers $headers `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
platform = 1
|
||||
installationId = $installationId
|
||||
deviceToken = "token-$stamp"
|
||||
deviceName = "Smoke Android $stamp"
|
||||
notificationsEnabled = $true
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$pushDevicesAfterRegister = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/push/devices" `
|
||||
-Headers $headers `
|
||||
-Method Get
|
||||
if ($null -eq $pushDevicesAfterRegister) {
|
||||
$pushDevicesAfterRegister = @()
|
||||
}
|
||||
else {
|
||||
$pushDevicesAfterRegister = @($pushDevicesAfterRegister)
|
||||
}
|
||||
|
||||
$bob = Invoke-RestMethod -Uri "$baseUrl/api/users/search?q=bob" -Headers $headers -Method Get | Select-Object -First 1
|
||||
$channel = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/chats/channel" `
|
||||
-Headers $headers `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
title = "Smoke Channel $stamp"
|
||||
memberIds = @($bob.id)
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$message = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/chats/$($channel.id)/messages" `
|
||||
-Headers $headers `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{ text = 'channel smoke message' } | ConvertTo-Json)
|
||||
|
||||
$edited = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/chats/$($channel.id)/messages/$($message.id)" `
|
||||
-Headers $headers `
|
||||
-Method Put `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{ text = 'channel smoke message edited' } | ConvertTo-Json)
|
||||
|
||||
$tempFile = Join-Path $env:TEMP "ikar-voice-$stamp.ogg"
|
||||
"voice smoke test $stamp" | Set-Content -Path $tempFile -NoNewline
|
||||
|
||||
$handler = New-Object System.Net.Http.HttpClientHandler
|
||||
$client = New-Object System.Net.Http.HttpClient($handler)
|
||||
$client.BaseAddress = [Uri]::new("$baseUrl/")
|
||||
$client.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue('Bearer', $refreshed.accessToken)
|
||||
$form = New-Object System.Net.Http.MultipartFormDataContent
|
||||
$form.Add((New-Object System.Net.Http.StringContent('voice caption')), 'text')
|
||||
$fileStream = [System.IO.File]::OpenRead($tempFile)
|
||||
$fileContent = New-Object System.Net.Http.StreamContent($fileStream)
|
||||
$fileContent.Headers.ContentType = New-Object System.Net.Http.Headers.MediaTypeHeaderValue('audio/ogg')
|
||||
$form.Add($fileContent, 'file', [System.IO.Path]::GetFileName($tempFile))
|
||||
$uploadResponse = $client.PostAsync("api/chats/$($channel.id)/attachments", $form).GetAwaiter().GetResult()
|
||||
$uploadPayload = $uploadResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult()
|
||||
$fileStream.Dispose()
|
||||
$form.Dispose()
|
||||
$client.Dispose()
|
||||
$handler.Dispose()
|
||||
|
||||
if (-not $uploadResponse.IsSuccessStatusCode) {
|
||||
throw $uploadPayload
|
||||
}
|
||||
|
||||
$voiceMessage = $uploadPayload | ConvertFrom-Json
|
||||
$attachment = $voiceMessage.attachments[0]
|
||||
$downloadHandler = New-Object System.Net.Http.HttpClientHandler
|
||||
$downloadClient = New-Object System.Net.Http.HttpClient($downloadHandler)
|
||||
$downloadClient.BaseAddress = [Uri]::new("$baseUrl/")
|
||||
$downloadClient.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue('Bearer', $refreshed.accessToken)
|
||||
$downloadResponse = $downloadClient.GetAsync($attachment.downloadPath.TrimStart('/')).GetAwaiter().GetResult()
|
||||
$downloadBytes = $downloadResponse.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
|
||||
$downloadClient.Dispose()
|
||||
$downloadHandler.Dispose()
|
||||
|
||||
$deleted = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/chats/$($channel.id)/messages/$($message.id)" `
|
||||
-Headers $headers `
|
||||
-Method Delete
|
||||
|
||||
$bobSession = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/auth/login" `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{
|
||||
username = 'bob'
|
||||
password = 'demo123'
|
||||
} | ConvertTo-Json)
|
||||
|
||||
$bobHeaders = @{
|
||||
Authorization = "Bearer $($bobSession.accessToken)"
|
||||
}
|
||||
|
||||
$subscriberBlocked = $false
|
||||
try {
|
||||
Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/chats/$($channel.id)/messages" `
|
||||
-Headers $bobHeaders `
|
||||
-Method Post `
|
||||
-ContentType 'application/json' `
|
||||
-Body (@{ text = 'subscriber should not publish' } | ConvertTo-Json) | Out-Null
|
||||
}
|
||||
catch {
|
||||
$subscriberBlocked = $_.Exception.Response.StatusCode.value__ -eq 403
|
||||
}
|
||||
|
||||
$channelForBob = Invoke-RestMethod -Uri "$baseUrl/api/chats/$($channel.id)" -Headers $bobHeaders -Method Get
|
||||
$channelReload = Invoke-RestMethod -Uri "$baseUrl/api/chats/$($channel.id)" -Headers $headers -Method Get
|
||||
Invoke-RestMethod -Uri "$baseUrl/api/push/devices/$installationId" -Headers $headers -Method Delete | Out-Null
|
||||
$pushDevicesAfterDelete = Invoke-RestMethod `
|
||||
-Uri "$baseUrl/api/push/devices" `
|
||||
-Headers $headers `
|
||||
-Method Get
|
||||
if ($null -eq $pushDevicesAfterDelete) {
|
||||
$pushDevicesAfterDelete = @()
|
||||
}
|
||||
else {
|
||||
$pushDevicesAfterDelete = @($pushDevicesAfterDelete)
|
||||
}
|
||||
Invoke-RestMethod -Uri "$baseUrl/api/auth/logout" -Headers $headers -Method Post | Out-Null
|
||||
|
||||
$logoutBlocked = $false
|
||||
try {
|
||||
Invoke-RestMethod -Uri "$baseUrl/api/users/me" -Headers $headers -Method Get | Out-Null
|
||||
}
|
||||
catch {
|
||||
$logoutBlocked = $_.Exception.Response.StatusCode.value__ -eq 401
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
Username = $username
|
||||
ChannelId = $channel.id
|
||||
EditedMessageId = $edited.id
|
||||
EditedText = $edited.text
|
||||
DeletedFlag = $null -ne $deleted.deletedAt
|
||||
VoiceAttachmentId = $attachment.id
|
||||
VoiceKind = $attachment.kind
|
||||
VoiceKindOk = ($attachment.kind -eq 2) -or ($attachment.kind -eq 'VoiceNote')
|
||||
DownloadOk = [int]$downloadResponse.StatusCode -eq 200 -and $downloadBytes.Length -eq $attachment.fileSizeBytes
|
||||
PushRegistered = $pushDevice.installationId -eq $installationId
|
||||
PushListed = $pushDevicesAfterRegister.Count -eq 1 -and $pushDevicesAfterRegister[0].installationId -eq $installationId
|
||||
PushDeleted = $pushDevicesAfterDelete.Count -eq 0
|
||||
MessageCount = $channelReload.messages.Count
|
||||
SubscriberBlocked = $subscriberBlocked
|
||||
ChannelReadOnly = -not $channelForBob.canSendMessages
|
||||
RefreshRotated = $session.refreshToken -ne $refreshed.refreshToken
|
||||
LogoutBlocked = $logoutBlocked
|
||||
}
|
||||
Reference in New Issue
Block a user