diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2509fdf..71a6437 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,12 +1,14 @@ name: CI on: push: - branches-ignore: - - 'generated' - - 'codegen/**' - - 'integrated/**' - - 'stl-preview-head/**' - - 'stl-preview-base/**' + branches: + - '**' + - '!integrated/**' + - '!stl-preview-head/**' + - '!stl-preview-base/**' + - '!generated' + - '!codegen/**' + - 'codegen/stl/**' pull_request: branches-ignore: - 'stl-preview-head/**' diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 098c0d1..b0cac61 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -18,5 +18,5 @@ jobs: run: | bash ./bin/check-release-environment env: - PACKAGIST_USERNAME: ${{ secrets.BEEPER_DESKTOP_PACKAGIST_USERNAME || secrets.PACKAGIST_USERNAME }} - PACKAGIST_SAFE_KEY: ${{ secrets.BEEPER_DESKTOP_PACKAGIST_SAFE_KEY || secrets.PACKAGIST_SAFE_KEY }} + PACKAGIST_USERNAME: ${{ secrets.BEEPER_PACKAGIST_USERNAME || secrets.PACKAGIST_USERNAME }} + PACKAGIST_SAFE_KEY: ${{ secrets.BEEPER_PACKAGIST_SAFE_KEY || secrets.PACKAGIST_SAFE_KEY }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1332969..8e76abb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.1" + ".": "5.0.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 8ec4701..2dd3fee 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 23 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-774bb08472b6bb14c280fe5b767925675516b5c8ccc0b89b5abd7ac7bc30fe5a.yml -openapi_spec_hash: ddd1ce1f334b45206ac008b0f5296842 -config_hash: b5ac0c1579dfe6257bcdb84cfd1002fc +configured_endpoints: 30 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper/beeper-desktop-api-c08c14bb754b4cb0e02b21fabb680469368286be339dec0aaa8c69d04a1f021a.yml +openapi_spec_hash: a10246aaf7cdc33b682fc245bd5f893b +config_hash: 72f9d43b9b51a5da912e9f3730e53ae2 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f084069 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog + +## 5.0.0 (2026-05-06) + +Full Changelog: [v0.0.1...v5.0.0](https://github.com/beeper/desktop-api-php/compare/v0.0.1...v5.0.0) + +### Features + +* **api:** add network, bridge fields to accounts ([8b25044](https://github.com/beeper/desktop-api-php/commit/8b250446d8d538fc1027c9df483f07034049e302)) +* **api:** api update ([d5d8954](https://github.com/beeper/desktop-api-php/commit/d5d89549d1dcdb03ebd079062c25917f76de162e)) +* **api:** api update ([48ca998](https://github.com/beeper/desktop-api-php/commit/48ca99875d9640e728922eab3eed6988fcd4ea14)) +* **api:** api update ([e098615](https://github.com/beeper/desktop-api-php/commit/e0986159d91fbd76d8647de9e2f7e2f1aedb071f)) +* **api:** api update ([fe6b606](https://github.com/beeper/desktop-api-php/commit/fe6b606f1e40643b6f81a68bfbf467653d070a6a)) +* **api:** api update ([f813329](https://github.com/beeper/desktop-api-php/commit/f8133290552840e3723a0f67371a4481039c5ac6)) +* **api:** api update ([1c754b3](https://github.com/beeper/desktop-api-php/commit/1c754b319cf35db0357cd97d9713119b78f70fc5)) +* **api:** manual updates ([46dbc09](https://github.com/beeper/desktop-api-php/commit/46dbc095defeac9aa95e41cd668e883fcd20bea9)) +* **api:** update via SDK Studio ([1c4d0e7](https://github.com/beeper/desktop-api-php/commit/1c4d0e7cb265c3bfcca18b886ec84d25eb7154d2)) +* **api:** update via SDK Studio ([e372296](https://github.com/beeper/desktop-api-php/commit/e372296e9885f6bb33819a22d29e92ac725395f1)) +* support setting headers via env ([91d9d0e](https://github.com/beeper/desktop-api-php/commit/91d9d0e7e5e35f90aa52187308f366ceb438080b)) + + +### Bug Fixes + +* **client:** properly generate file params ([44ea2d6](https://github.com/beeper/desktop-api-php/commit/44ea2d6f7210b88dd61b764242e7a68254f07efb)) +* **client:** resolve serialization issue with unions and enums ([7d947c2](https://github.com/beeper/desktop-api-php/commit/7d947c2190514f5039947136a1e795e69069d42b)) +* populate enum-typed properties with enum instances ([8c3b6fc](https://github.com/beeper/desktop-api-php/commit/8c3b6fc3be27e61cfb5556fab51f29bff5eae2e6)) +* revert enum parsing change that lead to unconditional failure ([82c146a](https://github.com/beeper/desktop-api-php/commit/82c146af6213f707f29059eed0d9061aa0555c4b)) + + +### Chores + +* **internal:** codegen related update ([297da59](https://github.com/beeper/desktop-api-php/commit/297da598fb4ba18fec57a63e461e4316f3d6e641)) +* **internal:** tweak CI branches ([d3967d0](https://github.com/beeper/desktop-api-php/commit/d3967d0433e63e905f0d9699da709a7544ec12db)) +* **internal:** update multipart form array serialization ([05282bb](https://github.com/beeper/desktop-api-php/commit/05282bbbaad46dceff167998b75e34a778f42a24)) +* **internal:** upgrade phpunit ([958db57](https://github.com/beeper/desktop-api-php/commit/958db5719fcca4f33c8b9c4796e34b8a3c5a8d91)) +* **test:** do not count install time for mock server timeout ([344cf76](https://github.com/beeper/desktop-api-php/commit/344cf76b4dc4c254d6c380f035e8b6d2d86fb138)) +* **tests:** bump steady to v0.19.4 ([5ead500](https://github.com/beeper/desktop-api-php/commit/5ead500c3d450d125dee608bc6ec0054e6928e15)) +* **tests:** bump steady to v0.19.5 ([1987ab0](https://github.com/beeper/desktop-api-php/commit/1987ab0fcc91f0bebfdf55a94fd5b6c59f3658e3)) +* **tests:** bump steady to v0.19.6 ([5299bea](https://github.com/beeper/desktop-api-php/commit/5299bea39412364752a5a21800bc9e5837cbce81)) +* **tests:** bump steady to v0.19.7 ([356dd2b](https://github.com/beeper/desktop-api-php/commit/356dd2b76fe8549a90e60013ad72e3fd5683e3cd)) +* **tests:** bump steady to v0.20.1 ([9a809f7](https://github.com/beeper/desktop-api-php/commit/9a809f782c368dbed079b96555a27f76f39a9b42)) +* **tests:** bump steady to v0.20.2 ([71daf35](https://github.com/beeper/desktop-api-php/commit/71daf3588b2f471df63c4070b121e8c5587e5727)) +* **tests:** bump steady to v0.22.1 ([192de54](https://github.com/beeper/desktop-api-php/commit/192de547707b52e2d5f60984af9d2c153bef2060)) + + +### Refactors + +* **tests:** switch from prism to steady ([b2a0994](https://github.com/beeper/desktop-api-php/commit/b2a099467ced88b63d36ae05aa4db7da5b5c64c8)) diff --git a/README.md b/README.md index b5d91b8..7ae7d70 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,14 @@ $client = new Client( accessToken: getenv('BEEPER_ACCESS_TOKEN') ?: 'My Access Token' ); -$page = $client->chats->search(includeMuted: true, limit: 3, type: 'single'); +$page = $client->chats->search( + accountIDs: [ + 'matrix', 'discordgo', 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc' + ], + includeMuted: true, + limit: 3, + type: 'single', +); var_dump($page->id); ``` @@ -70,9 +77,9 @@ $client = new Client( ); $page = $client->messages->search( - accountIDs: ['local-telegram_ba_QFrb5lrLPhO3OT5MFBeTWv0x4BI'], + accountIDs: ['discordgo', 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc'], limit: 10, - query: 'deployment', + query: 'oauth', ); var_dump($page); @@ -147,6 +154,36 @@ $client = new Client(requestOptions: ['maxRetries' => 0]); $result = $client->accounts->list(requestOptions: ['maxRetries' => 5]); ``` +### File uploads + +Request parameters that correspond to file uploads can be passed as a resource returned by `fopen()`, a string of file contents, or a `FileParam` instance. + +```php +assets->upload( + file: FileParam::fromString($contents, filename: '/path/to/file', contentType: '…'), +); + +// Pass in only a string (where applicable) +$response = $client->assets->upload(file: '…'); + +// Pass an open resource: +$fd = fopen('/path/to/file', 'r'); +try { + $response = $client->assets->upload( + file: FileParam::fromResource($fd, filename: '/path/to/file', contentType: '…'), + ); +} finally { + fclose($fd); +} +``` + ## Advanced concepts ### Making custom or undocumented requests diff --git a/composer.lock b/composer.lock index 3f6c209..7a5e63d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5fc63f7c84d94b42416689723c547f69", + "content-hash": "ffa287ea8babf60e021f37e62c6c207a", "packages": [ { "name": "php-http/discovery", @@ -194,16 +194,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v7.8.4", + "version": "v7.8.5", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "130a9bf0e269ee5f5b320108f794ad03e275cad4" + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/130a9bf0e269ee5f5b320108f794ad03e275cad4", - "reference": "130a9bf0e269ee5f5b320108f794ad03e275cad4", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", + "reference": "9b324c8fc319cf9728b581c7a90e1c8f6361c5e5", "shasum": "" }, "require": { @@ -211,27 +211,27 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^1.2.0", + "fidry/cpu-core-counter": "^1.3.0", "jean85/pretty-package-versions": "^2.1.1", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0", - "phpunit/php-code-coverage": "^11.0.10", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "phpunit/php-code-coverage": "^11.0.12", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.5.24", + "phpunit/phpunit": "^11.5.46", "sebastian/environment": "^7.2.1", - "symfony/console": "^6.4.22 || ^7.3.0", - "symfony/process": "^6.4.20 || ^7.3.0" + "symfony/console": "^6.4.22 || ^7.3.4 || ^8.0.3", + "symfony/process": "^6.4.20 || ^7.3.4 || ^8.0.3" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan": "^2.1.33", "phpstan/phpstan-deprecation-rules": "^2.0.3", - "phpstan/phpstan-phpunit": "^2.0.6", - "phpstan/phpstan-strict-rules": "^2.0.4", - "squizlabs/php_codesniffer": "^3.13.2", - "symfony/filesystem": "^6.4.13 || ^7.3.0" + "phpstan/phpstan-phpunit": "^2.0.11", + "phpstan/phpstan-strict-rules": "^2.0.7", + "squizlabs/php_codesniffer": "^3.13.5", + "symfony/filesystem": "^6.4.13 || ^7.3.2 || ^8.0.1" }, "bin": [ "bin/paratest", @@ -271,7 +271,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.8.4" + "source": "https://github.com/paratestphp/paratest/tree/v7.8.5" }, "funding": [ { @@ -283,7 +283,7 @@ "type": "paypal" } ], - "time": "2025-06-23T06:07:21+00:00" + "time": "2026-01-08T08:02:38+00:00" }, { "name": "clue/ndjson-react", @@ -1412,38 +1412,38 @@ }, { "name": "pestphp/pest", - "version": "v3.8.4", + "version": "v3.8.5", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "72cf695554420e21858cda831d5db193db102574" + "reference": "7796630eafcfd1c02660cecdde3bc6984fbf01f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/72cf695554420e21858cda831d5db193db102574", - "reference": "72cf695554420e21858cda831d5db193db102574", + "url": "https://api.github.com/repos/pestphp/pest/zipball/7796630eafcfd1c02660cecdde3bc6984fbf01f4", + "reference": "7796630eafcfd1c02660cecdde3bc6984fbf01f4", "shasum": "" }, "require": { - "brianium/paratest": "^7.8.4", - "nunomaduro/collision": "^8.8.2", - "nunomaduro/termwind": "^2.3.1", + "brianium/paratest": "^7.8.5", + "nunomaduro/collision": "^8.8.3", + "nunomaduro/termwind": "^2.3.3", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.1.1", "pestphp/pest-plugin-mutate": "^3.0.5", "php": "^8.2.0", - "phpunit/phpunit": "^11.5.33" + "phpunit/phpunit": "^11.5.50" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.33", + "phpunit/phpunit": ">11.5.50", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { "pestphp/pest-dev-tools": "^3.4.0", "pestphp/pest-plugin-type-coverage": "^3.6.1", - "symfony/process": "^7.3.0" + "symfony/process": "^7.4.4" }, "bin": [ "bin/pest" @@ -1508,7 +1508,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v3.8.4" + "source": "https://github.com/pestphp/pest/tree/v3.8.5" }, "funding": [ { @@ -1520,7 +1520,7 @@ "type": "github" } ], - "time": "2025-08-20T19:12:42+00:00" + "time": "2026-01-28T01:33:45+00:00" }, { "name": "pestphp/pest-plugin", @@ -2627,28 +2627,28 @@ }, { "name": "phpunit/php-file-iterator", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", - "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -2676,15 +2676,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" } ], - "time": "2024-08-27T05:02:59+00:00" + "time": "2026-02-02T13:52:54+00:00" }, { "name": "phpunit/php-invoker", @@ -2872,16 +2884,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.33", + "version": "11.5.50", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "5965e9ff57546cb9137c0ff6aa78cb7442b05cf6" + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5965e9ff57546cb9137c0ff6aa78cb7442b05cf6", - "reference": "5965e9ff57546cb9137c0ff6aa78cb7442b05cf6", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", + "reference": "fdfc727f0fcacfeb8fcb30c7e5da173125b58be3", "shasum": "" }, "require": { @@ -2895,17 +2907,17 @@ "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.10", + "phpunit/php-code-coverage": "^11.0.12", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.3", - "sebastian/comparator": "^6.3.2", + "sebastian/comparator": "^6.3.3", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.1", - "sebastian/exporter": "^6.3.0", + "sebastian/exporter": "^6.3.2", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", "sebastian/type": "^5.1.3", @@ -2953,7 +2965,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.33" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.50" }, "funding": [ { @@ -2977,7 +2989,7 @@ "type": "tidelift" } ], - "time": "2025-08-16T05:19:02+00:00" + "time": "2026-01-27T05:59:18+00:00" }, { "name": "psr/container", @@ -3936,16 +3948,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.2", + "version": "6.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", - "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", "shasum": "" }, "require": { @@ -4004,7 +4016,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" }, "funding": [ { @@ -4024,7 +4036,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T08:07:46+00:00" + "time": "2026-01-24T09:26:40+00:00" }, { "name": "sebastian/complexity", @@ -6668,5 +6680,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/scripts/mock b/scripts/mock index 0b28f6e..9c7c439 100755 --- a/scripts/mock +++ b/scripts/mock @@ -19,23 +19,34 @@ fi echo "==> Starting mock server with URL ${URL}" -# Run prism mock on the given spec +# Run steady mock on the given spec if [ "$1" == "--daemon" ]; then - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stdy/cli@0.22.1 -- steady --version - # Wait for server to come online + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & + + # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" - while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=0 + while ! curl --silent --fail "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1; do + if ! kill -0 $! 2>/dev/null; then + echo + cat .stdy.log + exit 1 + fi + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Steady server to start" + cat .stdy.log + exit 1 + fi echo -n "." sleep 0.1 done - if grep -q "✖ fatal" ".prism.log"; then - cat .prism.log - exit 1 - fi - echo else - npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index 4b777e0..bef4dad 100755 --- a/scripts/test +++ b/scripts/test @@ -9,8 +9,8 @@ GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' # No Color -function prism_is_running() { - curl --silent "http://localhost:4010" >/dev/null 2>&1 +function steady_is_running() { + curl --silent "http://127.0.0.1:4010/_x-steady/health" >/dev/null 2>&1 } kill_server_on_port() { @@ -25,7 +25,7 @@ function is_overriding_api_base_url() { [ -n "$TEST_API_BASE_URL" ] } -if ! is_overriding_api_base_url && ! prism_is_running ; then +if ! is_overriding_api_base_url && ! steady_is_running ; then # When we exit this script, make sure to kill the background mock server process trap 'kill_server_on_port 4010' EXIT @@ -36,19 +36,19 @@ fi if is_overriding_api_base_url ; then echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" echo -elif ! prism_is_running ; then - echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" +elif ! steady_is_running ; then + echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Steady server" echo -e "running against your OpenAPI spec." echo echo -e "To run the server, pass in the path or url of your OpenAPI" - echo -e "spec to the prism command:" + echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock path/to/your.openapi.yml${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.22.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 else - echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" + echo -e "${GREEN}✔ Mock steady server is running with your OpenAPI spec${NC}" echo fi diff --git a/src/Accounts/Account.php b/src/Accounts/Account.php index 270cb3e..6d4c449 100644 --- a/src/Accounts/Account.php +++ b/src/Accounts/Account.php @@ -4,6 +4,8 @@ namespace BeeperDesktop\Accounts; +use BeeperDesktop\Accounts\Account\Bridge; +use BeeperDesktop\Core\Attributes\Optional; use BeeperDesktop\Core\Attributes\Required; use BeeperDesktop\Core\Concerns\SdkModel; use BeeperDesktop\Core\Contracts\BaseModel; @@ -12,9 +14,15 @@ /** * A chat account added to Beeper. * + * @phpstan-import-type BridgeShape from \BeeperDesktop\Accounts\Account\Bridge * @phpstan-import-type UserShape from \BeeperDesktop\User * - * @phpstan-type AccountShape = array{accountID: string, user: User|UserShape} + * @phpstan-type AccountShape = array{ + * accountID: string, + * bridge: Bridge|BridgeShape, + * user: User|UserShape, + * network?: string|null, + * } */ final class Account implements BaseModel { @@ -22,29 +30,41 @@ final class Account implements BaseModel use SdkModel; /** - * Chat account added to Beeper. Use this to route account-scoped actions. + * Chat account added to Beeper. Use this to route account-scoped actions. Examples include matrix for Beeper/Matrix, discordgo for a cloud bridge, slackgo.TEAM-USER for workspace-scoped cloud bridges, and local-whatsapp_ba_... for local bridges. */ #[Required] public string $accountID; + /** + * Bridge metadata for the account. Available in Beeper Desktop v4.2.785+. + */ + #[Required] + public Bridge $bridge; + /** * User the account belongs to. */ #[Required] public User $user; + /** + * Human-friendly network name for the account. Omitted when the network is unknown. + */ + #[Optional] + public ?string $network; + /** * `new Account()` is missing required properties by the API. * * To enforce required parameters use * ``` - * Account::with(accountID: ..., user: ...) + * Account::with(accountID: ..., bridge: ..., user: ...) * ``` * * Otherwise ensure the following setters are called * * ``` - * (new Account)->withAccountID(...)->withUser(...) + * (new Account)->withAccountID(...)->withBridge(...)->withUser(...) * ``` */ public function __construct() @@ -57,20 +77,28 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * + * @param Bridge|BridgeShape $bridge * @param User|UserShape $user */ - public static function with(string $accountID, User|array $user): self - { + public static function with( + string $accountID, + Bridge|array $bridge, + User|array $user, + ?string $network = null, + ): self { $self = new self; $self['accountID'] = $accountID; + $self['bridge'] = $bridge; $self['user'] = $user; + null !== $network && $self['network'] = $network; + return $self; } /** - * Chat account added to Beeper. Use this to route account-scoped actions. + * Chat account added to Beeper. Use this to route account-scoped actions. Examples include matrix for Beeper/Matrix, discordgo for a cloud bridge, slackgo.TEAM-USER for workspace-scoped cloud bridges, and local-whatsapp_ba_... for local bridges. */ public function withAccountID(string $accountID): self { @@ -80,6 +108,19 @@ public function withAccountID(string $accountID): self return $self; } + /** + * Bridge metadata for the account. Available in Beeper Desktop v4.2.785+. + * + * @param Bridge|BridgeShape $bridge + */ + public function withBridge(Bridge|array $bridge): self + { + $self = clone $this; + $self['bridge'] = $bridge; + + return $self; + } + /** * User the account belongs to. * @@ -92,4 +133,15 @@ public function withUser(User|array $user): self return $self; } + + /** + * Human-friendly network name for the account. Omitted when the network is unknown. + */ + public function withNetwork(string $network): self + { + $self = clone $this; + $self['network'] = $network; + + return $self; + } } diff --git a/src/Accounts/Account/Bridge.php b/src/Accounts/Account/Bridge.php new file mode 100644 index 0000000..5651fc8 --- /dev/null +++ b/src/Accounts/Account/Bridge.php @@ -0,0 +1,118 @@ +, type: string + * } + */ +final class Bridge implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Bridge instance identifier. Matrix and cloud bridges often use the bridge type (for example matrix or discordgo); local bridges use a local bridge ID (for example local-whatsapp). Available in Beeper Desktop v4.2.785+. + */ + #[Required] + public string $id; + + /** + * Bridge provider for the account. Available in Beeper Desktop v4.2.785+. + * + * @var value-of $provider + */ + #[Required(enum: Provider::class)] + public string $provider; + + /** + * Bridge type, such as matrix, discordgo, slackgo, whatsapp, telegram, or twitter. Available in Beeper Desktop v4.2.785+. + */ + #[Required] + public string $type; + + /** + * `new Bridge()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Bridge::with(id: ..., provider: ..., type: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Bridge)->withID(...)->withProvider(...)->withType(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Provider|value-of $provider + */ + public static function with( + string $id, + Provider|string $provider, + string $type + ): self { + $self = new self; + + $self['id'] = $id; + $self['provider'] = $provider; + $self['type'] = $type; + + return $self; + } + + /** + * Bridge instance identifier. Matrix and cloud bridges often use the bridge type (for example matrix or discordgo); local bridges use a local bridge ID (for example local-whatsapp). Available in Beeper Desktop v4.2.785+. + */ + public function withID(string $id): self + { + $self = clone $this; + $self['id'] = $id; + + return $self; + } + + /** + * Bridge provider for the account. Available in Beeper Desktop v4.2.785+. + * + * @param Provider|value-of $provider + */ + public function withProvider(Provider|string $provider): self + { + $self = clone $this; + $self['provider'] = $provider; + + return $self; + } + + /** + * Bridge type, such as matrix, discordgo, slackgo, whatsapp, telegram, or twitter. Available in Beeper Desktop v4.2.785+. + */ + public function withType(string $type): self + { + $self = clone $this; + $self['type'] = $type; + + return $self; + } +} diff --git a/src/Accounts/Account/Bridge/Provider.php b/src/Accounts/Account/Bridge/Provider.php new file mode 100644 index 0000000..e8a0591 --- /dev/null +++ b/src/Accounts/Account/Bridge/Provider.php @@ -0,0 +1,19 @@ +, @@ -27,6 +29,7 @@ * posterImg?: string|null, * size?: null|Size|SizeShape, * srcURL?: string|null, + * transcription?: null|Transcription|TranscriptionShape, * } */ final class Attachment implements BaseModel @@ -43,7 +46,7 @@ final class Attachment implements BaseModel public string $type; /** - * Attachment identifier (typically an mxc:// URL). Use with /v1/assets/download to get a local file path. + * Attachment identifier (typically an mxc:// URL). Use the download file endpoint to get a local file path. */ #[Optional] public ?string $id; @@ -103,11 +106,17 @@ final class Attachment implements BaseModel public ?Size $size; /** - * Public URL or local file path to fetch the asset. May be temporary or local-only to this device; download promptly if durable access is needed. + * Public URL or local file path to fetch the file. May be temporary or local-only to this device; download promptly if durable access is needed. */ #[Optional] public ?string $srcURL; + /** + * Attachment transcription if available. + */ + #[Optional] + public ?Transcription $transcription; + /** * `new Attachment()` is missing required properties by the API. * @@ -134,6 +143,7 @@ public function __construct() * * @param Type|value-of $type * @param Size|SizeShape|null $size + * @param Transcription|TranscriptionShape|null $transcription */ public static function with( Type|string $type, @@ -148,6 +158,7 @@ public static function with( ?string $posterImg = null, Size|array|null $size = null, ?string $srcURL = null, + Transcription|array|null $transcription = null, ): self { $self = new self; @@ -164,6 +175,7 @@ public static function with( null !== $posterImg && $self['posterImg'] = $posterImg; null !== $size && $self['size'] = $size; null !== $srcURL && $self['srcURL'] = $srcURL; + null !== $transcription && $self['transcription'] = $transcription; return $self; } @@ -182,7 +194,7 @@ public function withType(Type|string $type): self } /** - * Attachment identifier (typically an mxc:// URL). Use with /v1/assets/download to get a local file path. + * Attachment identifier (typically an mxc:// URL). Use the download file endpoint to get a local file path. */ public function withID(string $id): self { @@ -294,7 +306,7 @@ public function withSize(Size|array $size): self } /** - * Public URL or local file path to fetch the asset. May be temporary or local-only to this device; download promptly if durable access is needed. + * Public URL or local file path to fetch the file. May be temporary or local-only to this device; download promptly if durable access is needed. */ public function withSrcURL(string $srcURL): self { @@ -303,4 +315,17 @@ public function withSrcURL(string $srcURL): self return $self; } + + /** + * Attachment transcription if available. + * + * @param Transcription|TranscriptionShape $transcription + */ + public function withTranscription(Transcription|array $transcription): self + { + $self = clone $this; + $self['transcription'] = $transcription; + + return $self; + } } diff --git a/src/Attachment/Transcription.php b/src/Attachment/Transcription.php new file mode 100644 index 0000000..957c2d5 --- /dev/null +++ b/src/Attachment/Transcription.php @@ -0,0 +1,113 @@ + */ + use SdkModel; + + /** + * Transcription engine. + */ + #[Required] + public string $engine; + + /** + * Transcribed text. + */ + #[Required] + public string $transcription; + + /** + * Detected or selected language. + */ + #[Optional] + public ?string $language; + + /** + * `new Transcription()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Transcription::with(engine: ..., transcription: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Transcription)->withEngine(...)->withTranscription(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with( + string $engine, + string $transcription, + ?string $language = null + ): self { + $self = new self; + + $self['engine'] = $engine; + $self['transcription'] = $transcription; + + null !== $language && $self['language'] = $language; + + return $self; + } + + /** + * Transcription engine. + */ + public function withEngine(string $engine): self + { + $self = clone $this; + $self['engine'] = $engine; + + return $self; + } + + /** + * Transcribed text. + */ + public function withTranscription(string $transcription): self + { + $self = clone $this; + $self['transcription'] = $transcription; + + return $self; + } + + /** + * Detected or selected language. + */ + public function withLanguage(string $language): self + { + $self = clone $this; + $self['language'] = $language; + + return $self; + } +} diff --git a/src/BeeperDesktopFocusParams.php b/src/BeeperDesktopFocusParams.php index 94b8844..7aa7429 100644 --- a/src/BeeperDesktopFocusParams.php +++ b/src/BeeperDesktopFocusParams.php @@ -10,7 +10,7 @@ use BeeperDesktop\Core\Contracts\BaseModel; /** - * Focus Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill draft text and attachment. + * Focus Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill plain text and an image path. * * @see BeeperDesktop\Services\BeeperDesktopClientService::focus() * @@ -34,13 +34,13 @@ final class BeeperDesktopFocusParams implements BaseModel public ?string $chatID; /** - * Optional draft attachment path to populate in the message input field. + * Optional image path to populate in the message input field. */ #[Optional] public ?string $draftAttachmentPath; /** - * Optional draft text to populate in the message input field. + * Optional plain text to populate in the message input field. */ #[Optional] public ?string $draftText; @@ -89,7 +89,7 @@ public function withChatID(string $chatID): self } /** - * Optional draft attachment path to populate in the message input field. + * Optional image path to populate in the message input field. */ public function withDraftAttachmentPath(string $draftAttachmentPath): self { @@ -100,7 +100,7 @@ public function withDraftAttachmentPath(string $draftAttachmentPath): self } /** - * Optional draft text to populate in the message input field. + * Optional plain text to populate in the message input field. */ public function withDraftText(string $draftText): self { diff --git a/src/Chats/Chat.php b/src/Chats/Chat.php index 8beb567..fbaa5e9 100644 --- a/src/Chats/Chat.php +++ b/src/Chats/Chat.php @@ -4,7 +4,11 @@ namespace BeeperDesktop\Chats; +use BeeperDesktop\Chats\Chat\Capabilities; +use BeeperDesktop\Chats\Chat\Draft; use BeeperDesktop\Chats\Chat\Participants; +use BeeperDesktop\Chats\Chat\Reminder; +use BeeperDesktop\Chats\Chat\Snooze; use BeeperDesktop\Chats\Chat\Type; use BeeperDesktop\Core\Attributes\Optional; use BeeperDesktop\Core\Attributes\Required; @@ -13,20 +17,36 @@ /** * @phpstan-import-type ParticipantsShape from \BeeperDesktop\Chats\Chat\Participants + * @phpstan-import-type CapabilitiesShape from \BeeperDesktop\Chats\Chat\Capabilities + * @phpstan-import-type DraftShape from \BeeperDesktop\Chats\Chat\Draft + * @phpstan-import-type ReminderShape from \BeeperDesktop\Chats\Chat\Reminder + * @phpstan-import-type SnoozeShape from \BeeperDesktop\Chats\Chat\Snooze * * @phpstan-type ChatShape = array{ * id: string, * accountID: string, + * network: string, * participants: Participants|ParticipantsShape, * title: string, * type: Type|value-of, * unreadCount: int, + * capabilities?: null|Capabilities|CapabilitiesShape, + * description?: string|null, + * draft?: null|Draft|DraftShape, + * imgURL?: string|null, * isArchived?: bool|null, + * isLowPriority?: bool|null, + * isMarkedUnread?: bool|null, * isMuted?: bool|null, * isPinned?: bool|null, + * isReadOnly?: bool|null, * lastActivity?: \DateTimeInterface|null, * lastReadMessageSortKey?: string|null, * localChatID?: string|null, + * messageExpirySeconds?: int|null, + * reminder?: null|Reminder|ReminderShape, + * snooze?: null|Snooze|SnoozeShape, + * unreadMentionsCount?: int|null, * } */ final class Chat implements BaseModel @@ -46,6 +66,12 @@ final class Chat implements BaseModel #[Required] public string $accountID; + /** + * Display-only human-readable account/network name. + */ + #[Required] + public string $network; + /** * Chat participants information. */ @@ -72,12 +98,48 @@ final class Chat implements BaseModel #[Required] public int $unreadCount; + /** + * Chat capabilities reported by the platform. + */ + #[Optional] + public ?Capabilities $capabilities; + + /** + * Group chat description/topic when available. + */ + #[Optional(nullable: true)] + public ?string $description; + + /** + * Current draft object for this chat, or null when no draft is set. + */ + #[Optional(nullable: true)] + public ?Draft $draft; + + /** + * Local filesystem path to the chat avatar image when available. + */ + #[Optional(nullable: true)] + public ?string $imgURL; + /** * True if chat is archived. */ #[Optional] public ?bool $isArchived; + /** + * True if chat is marked low priority. + */ + #[Optional] + public ?bool $isLowPriority; + + /** + * True if the chat was explicitly marked unread by the authenticated user. + */ + #[Optional] + public ?bool $isMarkedUnread; + /** * True if chat notifications are muted. */ @@ -90,6 +152,12 @@ final class Chat implements BaseModel #[Optional] public ?bool $isPinned; + /** + * True if messages cannot be sent in this chat. + */ + #[Optional] + public ?bool $isReadOnly; + /** * Timestamp of last activity. */ @@ -108,6 +176,30 @@ final class Chat implements BaseModel #[Optional(nullable: true)] public ?string $localChatID; + /** + * Disappearing-message timer in seconds when available. + */ + #[Optional(nullable: true)] + public ?int $messageExpirySeconds; + + /** + * Current reminder for this chat, or null when no reminder is set. + */ + #[Optional(nullable: true)] + public ?Reminder $reminder; + + /** + * Current snooze state for this chat, or null when no snooze is set. + */ + #[Optional(nullable: true)] + public ?Snooze $snooze; + + /** + * Number of unread messages that mention the authenticated user or @room. + */ + #[Optional] + public ?int $unreadMentionsCount; + /** * `new Chat()` is missing required properties by the API. * @@ -116,6 +208,7 @@ final class Chat implements BaseModel * Chat::with( * id: ..., * accountID: ..., + * network: ..., * participants: ..., * title: ..., * type: ..., @@ -129,6 +222,7 @@ final class Chat implements BaseModel * (new Chat) * ->withID(...) * ->withAccountID(...) + * ->withNetwork(...) * ->withParticipants(...) * ->withTitle(...) * ->withType(...) @@ -147,36 +241,64 @@ public function __construct() * * @param Participants|ParticipantsShape $participants * @param Type|value-of $type + * @param Capabilities|CapabilitiesShape|null $capabilities + * @param Draft|DraftShape|null $draft + * @param Reminder|ReminderShape|null $reminder + * @param Snooze|SnoozeShape|null $snooze */ public static function with( string $id, string $accountID, + string $network, Participants|array $participants, string $title, Type|string $type, int $unreadCount, + Capabilities|array|null $capabilities = null, + ?string $description = null, + Draft|array|null $draft = null, + ?string $imgURL = null, ?bool $isArchived = null, + ?bool $isLowPriority = null, + ?bool $isMarkedUnread = null, ?bool $isMuted = null, ?bool $isPinned = null, + ?bool $isReadOnly = null, ?\DateTimeInterface $lastActivity = null, ?string $lastReadMessageSortKey = null, ?string $localChatID = null, + ?int $messageExpirySeconds = null, + Reminder|array|null $reminder = null, + Snooze|array|null $snooze = null, + ?int $unreadMentionsCount = null, ): self { $self = new self; $self['id'] = $id; $self['accountID'] = $accountID; + $self['network'] = $network; $self['participants'] = $participants; $self['title'] = $title; $self['type'] = $type; $self['unreadCount'] = $unreadCount; + null !== $capabilities && $self['capabilities'] = $capabilities; + null !== $description && $self['description'] = $description; + null !== $draft && $self['draft'] = $draft; + null !== $imgURL && $self['imgURL'] = $imgURL; null !== $isArchived && $self['isArchived'] = $isArchived; + null !== $isLowPriority && $self['isLowPriority'] = $isLowPriority; + null !== $isMarkedUnread && $self['isMarkedUnread'] = $isMarkedUnread; null !== $isMuted && $self['isMuted'] = $isMuted; null !== $isPinned && $self['isPinned'] = $isPinned; + null !== $isReadOnly && $self['isReadOnly'] = $isReadOnly; null !== $lastActivity && $self['lastActivity'] = $lastActivity; null !== $lastReadMessageSortKey && $self['lastReadMessageSortKey'] = $lastReadMessageSortKey; null !== $localChatID && $self['localChatID'] = $localChatID; + null !== $messageExpirySeconds && $self['messageExpirySeconds'] = $messageExpirySeconds; + null !== $reminder && $self['reminder'] = $reminder; + null !== $snooze && $self['snooze'] = $snooze; + null !== $unreadMentionsCount && $self['unreadMentionsCount'] = $unreadMentionsCount; return $self; } @@ -203,6 +325,17 @@ public function withAccountID(string $accountID): self return $self; } + /** + * Display-only human-readable account/network name. + */ + public function withNetwork(string $network): self + { + $self = clone $this; + $self['network'] = $network; + + return $self; + } + /** * Chat participants information. * @@ -251,6 +384,54 @@ public function withUnreadCount(int $unreadCount): self return $self; } + /** + * Chat capabilities reported by the platform. + * + * @param Capabilities|CapabilitiesShape $capabilities + */ + public function withCapabilities(Capabilities|array $capabilities): self + { + $self = clone $this; + $self['capabilities'] = $capabilities; + + return $self; + } + + /** + * Group chat description/topic when available. + */ + public function withDescription(?string $description): self + { + $self = clone $this; + $self['description'] = $description; + + return $self; + } + + /** + * Current draft object for this chat, or null when no draft is set. + * + * @param Draft|DraftShape|null $draft + */ + public function withDraft(Draft|array|null $draft): self + { + $self = clone $this; + $self['draft'] = $draft; + + return $self; + } + + /** + * Local filesystem path to the chat avatar image when available. + */ + public function withImgURL(?string $imgURL): self + { + $self = clone $this; + $self['imgURL'] = $imgURL; + + return $self; + } + /** * True if chat is archived. */ @@ -262,6 +443,28 @@ public function withIsArchived(bool $isArchived): self return $self; } + /** + * True if chat is marked low priority. + */ + public function withIsLowPriority(bool $isLowPriority): self + { + $self = clone $this; + $self['isLowPriority'] = $isLowPriority; + + return $self; + } + + /** + * True if the chat was explicitly marked unread by the authenticated user. + */ + public function withIsMarkedUnread(bool $isMarkedUnread): self + { + $self = clone $this; + $self['isMarkedUnread'] = $isMarkedUnread; + + return $self; + } + /** * True if chat notifications are muted. */ @@ -284,6 +487,17 @@ public function withIsPinned(bool $isPinned): self return $self; } + /** + * True if messages cannot be sent in this chat. + */ + public function withIsReadOnly(bool $isReadOnly): self + { + $self = clone $this; + $self['isReadOnly'] = $isReadOnly; + + return $self; + } + /** * Timestamp of last activity. */ @@ -317,4 +531,52 @@ public function withLocalChatID(?string $localChatID): self return $self; } + + /** + * Disappearing-message timer in seconds when available. + */ + public function withMessageExpirySeconds(?int $messageExpirySeconds): self + { + $self = clone $this; + $self['messageExpirySeconds'] = $messageExpirySeconds; + + return $self; + } + + /** + * Current reminder for this chat, or null when no reminder is set. + * + * @param Reminder|ReminderShape|null $reminder + */ + public function withReminder(Reminder|array|null $reminder): self + { + $self = clone $this; + $self['reminder'] = $reminder; + + return $self; + } + + /** + * Current snooze state for this chat, or null when no snooze is set. + * + * @param Snooze|SnoozeShape|null $snooze + */ + public function withSnooze(Snooze|array|null $snooze): self + { + $self = clone $this; + $self['snooze'] = $snooze; + + return $self; + } + + /** + * Number of unread messages that mention the authenticated user or @room. + */ + public function withUnreadMentionsCount(int $unreadMentionsCount): self + { + $self = clone $this; + $self['unreadMentionsCount'] = $unreadMentionsCount; + + return $self; + } } diff --git a/src/Chats/Chat/Capabilities.php b/src/Chats/Chat/Capabilities.php new file mode 100644 index 0000000..d60c40a --- /dev/null +++ b/src/Chats/Chat/Capabilities.php @@ -0,0 +1,665 @@ +|null, + * archive?: bool|null, + * attachments?: array|null, + * customEmojiReactions?: bool|null, + * delete?: null|Delete|value-of, + * deleteChat?: bool|null, + * deleteChatForEveryone?: bool|null, + * deleteForMe?: bool|null, + * deleteMaxAge?: int|null, + * disappearingTimer?: null|DisappearingTimer|DisappearingTimerShape, + * edit?: null|Edit|value-of, + * editMaxAge?: int|null, + * editMaxCount?: int|null, + * formatting?: array>|null, + * locationMessage?: null|LocationMessage|value-of, + * markAsUnread?: bool|null, + * maxTextLength?: int|null, + * messageRequest?: null|MessageRequest|MessageRequestShape, + * participantActions?: null|ParticipantActions|ParticipantActionsShape, + * poll?: null|Poll|value-of, + * reaction?: null|Reaction|value-of, + * reactionCount?: int|null, + * readReceipts?: bool|null, + * reply?: null|Reply|value-of, + * state?: null|State|StateShape, + * thread?: null|Thread|value-of, + * typingNotifications?: bool|null, + * } + */ +final class Capabilities implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Allowed Unicode reactions. Omitted means all emoji reactions are allowed. + * + * @var list|null $allowedReactions + */ + #[Optional(list: 'string')] + public ?array $allowedReactions; + + /** + * True if archive/unarchive is supported. + */ + #[Optional] + public ?bool $archive; + + /** + * Supported attachment message types and their per-type constraints, keyed by Matrix msgtype or pseudo-msgtype (for example m.image, m.video, org.matrix.msc3245.voice). Missing message types should be treated as rejected. + * + * @var array|null $attachments + */ + #[Optional(map: Attachment::class)] + public ?array $attachments; + + /** + * True if custom emoji reactions are supported. + */ + #[Optional] + public ?bool $customEmojiReactions; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $delete + */ + #[Optional(enum: Delete::class)] + public ?int $delete; + + /** + * True if deleting chats for the authenticated user is supported. + */ + #[Optional] + public ?bool $deleteChat; + + /** + * True if deleting chats for everyone is supported. + */ + #[Optional] + public ?bool $deleteChatForEveryone; + + /** + * True if deleting messages only for the authenticated user is supported. + */ + #[Optional] + public ?bool $deleteForMe; + + /** + * Maximum message age for delete-for-everyone, in seconds. + */ + #[Optional] + public ?int $deleteMaxAge; + + /** + * Disappearing-message timer capabilities. + */ + #[Optional] + public ?DisappearingTimer $disappearingTimer; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $edit + */ + #[Optional(enum: Edit::class)] + public ?int $edit; + + /** + * Maximum message age for edits, in seconds. + */ + #[Optional] + public ?int $editMaxAge; + + /** + * Maximum number of edits allowed for one message. + */ + #[Optional] + public ?int $editMaxCount; + + /** + * Supported rich-text formatting features keyed by feature name (for example bold, inline_code, code_block.syntax_highlighting). Omitted means no formatting support is advertised. + * + * @var array>|null $formatting + */ + #[Optional(map: Formatting::class)] + public ?array $formatting; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $locationMessage + */ + #[Optional(enum: LocationMessage::class)] + public ?int $locationMessage; + + /** + * True if marking chats unread is supported. + */ + #[Optional] + public ?bool $markAsUnread; + + /** + * Maximum length of normal text messages. + */ + #[Optional] + public ?int $maxTextLength; + + /** + * Message request capabilities. + */ + #[Optional] + public ?MessageRequest $messageRequest; + + /** + * Participant management capabilities. + */ + #[Optional] + public ?ParticipantActions $participantActions; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $poll + */ + #[Optional(enum: Poll::class)] + public ?int $poll; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $reaction + */ + #[Optional(enum: Reaction::class)] + public ?int $reaction; + + /** + * Maximum number of reactions allowed on a single message. + */ + #[Optional] + public ?int $reactionCount; + + /** + * True if read receipts are supported. + */ + #[Optional] + public ?bool $readReceipts; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $reply + */ + #[Optional(enum: Reply::class)] + public ?int $reply; + + /** + * Chat state update capabilities. + */ + #[Optional] + public ?State $state; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $thread + */ + #[Optional(enum: Thread::class)] + public ?int $thread; + + /** + * True if typing notifications are supported. + */ + #[Optional] + public ?bool $typingNotifications; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param list|null $allowedReactions + * @param array|null $attachments + * @param Delete|value-of|null $delete + * @param DisappearingTimer|DisappearingTimerShape|null $disappearingTimer + * @param Edit|value-of|null $edit + * @param array>|null $formatting + * @param LocationMessage|value-of|null $locationMessage + * @param MessageRequest|MessageRequestShape|null $messageRequest + * @param ParticipantActions|ParticipantActionsShape|null $participantActions + * @param Poll|value-of|null $poll + * @param Reaction|value-of|null $reaction + * @param Reply|value-of|null $reply + * @param State|StateShape|null $state + * @param Thread|value-of|null $thread + */ + public static function with( + ?array $allowedReactions = null, + ?bool $archive = null, + ?array $attachments = null, + ?bool $customEmojiReactions = null, + Delete|int|null $delete = null, + ?bool $deleteChat = null, + ?bool $deleteChatForEveryone = null, + ?bool $deleteForMe = null, + ?int $deleteMaxAge = null, + DisappearingTimer|array|null $disappearingTimer = null, + Edit|int|null $edit = null, + ?int $editMaxAge = null, + ?int $editMaxCount = null, + ?array $formatting = null, + LocationMessage|int|null $locationMessage = null, + ?bool $markAsUnread = null, + ?int $maxTextLength = null, + MessageRequest|array|null $messageRequest = null, + ParticipantActions|array|null $participantActions = null, + Poll|int|null $poll = null, + Reaction|int|null $reaction = null, + ?int $reactionCount = null, + ?bool $readReceipts = null, + Reply|int|null $reply = null, + State|array|null $state = null, + Thread|int|null $thread = null, + ?bool $typingNotifications = null, + ): self { + $self = new self; + + null !== $allowedReactions && $self['allowedReactions'] = $allowedReactions; + null !== $archive && $self['archive'] = $archive; + null !== $attachments && $self['attachments'] = $attachments; + null !== $customEmojiReactions && $self['customEmojiReactions'] = $customEmojiReactions; + null !== $delete && $self['delete'] = $delete; + null !== $deleteChat && $self['deleteChat'] = $deleteChat; + null !== $deleteChatForEveryone && $self['deleteChatForEveryone'] = $deleteChatForEveryone; + null !== $deleteForMe && $self['deleteForMe'] = $deleteForMe; + null !== $deleteMaxAge && $self['deleteMaxAge'] = $deleteMaxAge; + null !== $disappearingTimer && $self['disappearingTimer'] = $disappearingTimer; + null !== $edit && $self['edit'] = $edit; + null !== $editMaxAge && $self['editMaxAge'] = $editMaxAge; + null !== $editMaxCount && $self['editMaxCount'] = $editMaxCount; + null !== $formatting && $self['formatting'] = $formatting; + null !== $locationMessage && $self['locationMessage'] = $locationMessage; + null !== $markAsUnread && $self['markAsUnread'] = $markAsUnread; + null !== $maxTextLength && $self['maxTextLength'] = $maxTextLength; + null !== $messageRequest && $self['messageRequest'] = $messageRequest; + null !== $participantActions && $self['participantActions'] = $participantActions; + null !== $poll && $self['poll'] = $poll; + null !== $reaction && $self['reaction'] = $reaction; + null !== $reactionCount && $self['reactionCount'] = $reactionCount; + null !== $readReceipts && $self['readReceipts'] = $readReceipts; + null !== $reply && $self['reply'] = $reply; + null !== $state && $self['state'] = $state; + null !== $thread && $self['thread'] = $thread; + null !== $typingNotifications && $self['typingNotifications'] = $typingNotifications; + + return $self; + } + + /** + * Allowed Unicode reactions. Omitted means all emoji reactions are allowed. + * + * @param list $allowedReactions + */ + public function withAllowedReactions(array $allowedReactions): self + { + $self = clone $this; + $self['allowedReactions'] = $allowedReactions; + + return $self; + } + + /** + * True if archive/unarchive is supported. + */ + public function withArchive(bool $archive): self + { + $self = clone $this; + $self['archive'] = $archive; + + return $self; + } + + /** + * Supported attachment message types and their per-type constraints, keyed by Matrix msgtype or pseudo-msgtype (for example m.image, m.video, org.matrix.msc3245.voice). Missing message types should be treated as rejected. + * + * @param array $attachments + */ + public function withAttachments(array $attachments): self + { + $self = clone $this; + $self['attachments'] = $attachments; + + return $self; + } + + /** + * True if custom emoji reactions are supported. + */ + public function withCustomEmojiReactions(bool $customEmojiReactions): self + { + $self = clone $this; + $self['customEmojiReactions'] = $customEmojiReactions; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Delete|value-of $delete + */ + public function withDelete(Delete|int $delete): self + { + $self = clone $this; + $self['delete'] = $delete; + + return $self; + } + + /** + * True if deleting chats for the authenticated user is supported. + */ + public function withDeleteChat(bool $deleteChat): self + { + $self = clone $this; + $self['deleteChat'] = $deleteChat; + + return $self; + } + + /** + * True if deleting chats for everyone is supported. + */ + public function withDeleteChatForEveryone(bool $deleteChatForEveryone): self + { + $self = clone $this; + $self['deleteChatForEveryone'] = $deleteChatForEveryone; + + return $self; + } + + /** + * True if deleting messages only for the authenticated user is supported. + */ + public function withDeleteForMe(bool $deleteForMe): self + { + $self = clone $this; + $self['deleteForMe'] = $deleteForMe; + + return $self; + } + + /** + * Maximum message age for delete-for-everyone, in seconds. + */ + public function withDeleteMaxAge(int $deleteMaxAge): self + { + $self = clone $this; + $self['deleteMaxAge'] = $deleteMaxAge; + + return $self; + } + + /** + * Disappearing-message timer capabilities. + * + * @param DisappearingTimer|DisappearingTimerShape $disappearingTimer + */ + public function withDisappearingTimer( + DisappearingTimer|array $disappearingTimer + ): self { + $self = clone $this; + $self['disappearingTimer'] = $disappearingTimer; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Edit|value-of $edit + */ + public function withEdit(Edit|int $edit): self + { + $self = clone $this; + $self['edit'] = $edit; + + return $self; + } + + /** + * Maximum message age for edits, in seconds. + */ + public function withEditMaxAge(int $editMaxAge): self + { + $self = clone $this; + $self['editMaxAge'] = $editMaxAge; + + return $self; + } + + /** + * Maximum number of edits allowed for one message. + */ + public function withEditMaxCount(int $editMaxCount): self + { + $self = clone $this; + $self['editMaxCount'] = $editMaxCount; + + return $self; + } + + /** + * Supported rich-text formatting features keyed by feature name (for example bold, inline_code, code_block.syntax_highlighting). Omitted means no formatting support is advertised. + * + * @param array> $formatting + */ + public function withFormatting(array $formatting): self + { + $self = clone $this; + $self['formatting'] = $formatting; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param LocationMessage|value-of $locationMessage + */ + public function withLocationMessage( + LocationMessage|int $locationMessage + ): self { + $self = clone $this; + $self['locationMessage'] = $locationMessage; + + return $self; + } + + /** + * True if marking chats unread is supported. + */ + public function withMarkAsUnread(bool $markAsUnread): self + { + $self = clone $this; + $self['markAsUnread'] = $markAsUnread; + + return $self; + } + + /** + * Maximum length of normal text messages. + */ + public function withMaxTextLength(int $maxTextLength): self + { + $self = clone $this; + $self['maxTextLength'] = $maxTextLength; + + return $self; + } + + /** + * Message request capabilities. + * + * @param MessageRequest|MessageRequestShape $messageRequest + */ + public function withMessageRequest( + MessageRequest|array $messageRequest + ): self { + $self = clone $this; + $self['messageRequest'] = $messageRequest; + + return $self; + } + + /** + * Participant management capabilities. + * + * @param ParticipantActions|ParticipantActionsShape $participantActions + */ + public function withParticipantActions( + ParticipantActions|array $participantActions + ): self { + $self = clone $this; + $self['participantActions'] = $participantActions; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Poll|value-of $poll + */ + public function withPoll(Poll|int $poll): self + { + $self = clone $this; + $self['poll'] = $poll; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Reaction|value-of $reaction + */ + public function withReaction(Reaction|int $reaction): self + { + $self = clone $this; + $self['reaction'] = $reaction; + + return $self; + } + + /** + * Maximum number of reactions allowed on a single message. + */ + public function withReactionCount(int $reactionCount): self + { + $self = clone $this; + $self['reactionCount'] = $reactionCount; + + return $self; + } + + /** + * True if read receipts are supported. + */ + public function withReadReceipts(bool $readReceipts): self + { + $self = clone $this; + $self['readReceipts'] = $readReceipts; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Reply|value-of $reply + */ + public function withReply(Reply|int $reply): self + { + $self = clone $this; + $self['reply'] = $reply; + + return $self; + } + + /** + * Chat state update capabilities. + * + * @param State|StateShape $state + */ + public function withState(State|array $state): self + { + $self = clone $this; + $self['state'] = $state; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Thread|value-of $thread + */ + public function withThread(Thread|int $thread): self + { + $self = clone $this; + $self['thread'] = $thread; + + return $self; + } + + /** + * True if typing notifications are supported. + */ + public function withTypingNotifications(bool $typingNotifications): self + { + $self = clone $this; + $self['typingNotifications'] = $typingNotifications; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/Attachment.php b/src/Chats/Chat/Capabilities/Attachment.php new file mode 100644 index 0000000..a6d3426 --- /dev/null +++ b/src/Chats/Chat/Capabilities/Attachment.php @@ -0,0 +1,228 @@ +>, + * caption?: null|Caption|value-of, + * maxCaptionLength?: int|null, + * maxDuration?: int|null, + * maxHeight?: int|null, + * maxSize?: int|null, + * maxWidth?: int|null, + * viewOnce?: bool|null, + * } + */ +final class Attachment implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Supported MIME types or MIME patterns for this file message type. Missing MIME types should be treated as rejected. + * + * @var array> $mimeTypes + */ + #[Required(map: MimeType::class)] + public array $mimeTypes; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $caption + */ + #[Optional(enum: Caption::class)] + public ?int $caption; + + /** + * Maximum caption length when captions are supported. + */ + #[Optional] + public ?int $maxCaptionLength; + + /** + * Maximum audio or video duration in seconds. + */ + #[Optional] + public ?int $maxDuration; + + /** + * Maximum image or video height in pixels. + */ + #[Optional] + public ?int $maxHeight; + + /** + * Maximum file size in bytes. + */ + #[Optional] + public ?int $maxSize; + + /** + * Maximum image or video width in pixels. + */ + #[Optional] + public ?int $maxWidth; + + /** + * True if this file type can be sent as view-once media. + */ + #[Optional] + public ?bool $viewOnce; + + /** + * `new Attachment()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Attachment::with(mimeTypes: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Attachment)->withMimeTypes(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param array> $mimeTypes + * @param Caption|value-of|null $caption + */ + public static function with( + array $mimeTypes, + Caption|int|null $caption = null, + ?int $maxCaptionLength = null, + ?int $maxDuration = null, + ?int $maxHeight = null, + ?int $maxSize = null, + ?int $maxWidth = null, + ?bool $viewOnce = null, + ): self { + $self = new self; + + $self['mimeTypes'] = $mimeTypes; + + null !== $caption && $self['caption'] = $caption; + null !== $maxCaptionLength && $self['maxCaptionLength'] = $maxCaptionLength; + null !== $maxDuration && $self['maxDuration'] = $maxDuration; + null !== $maxHeight && $self['maxHeight'] = $maxHeight; + null !== $maxSize && $self['maxSize'] = $maxSize; + null !== $maxWidth && $self['maxWidth'] = $maxWidth; + null !== $viewOnce && $self['viewOnce'] = $viewOnce; + + return $self; + } + + /** + * Supported MIME types or MIME patterns for this file message type. Missing MIME types should be treated as rejected. + * + * @param array> $mimeTypes + */ + public function withMimeTypes(array $mimeTypes): self + { + $self = clone $this; + $self['mimeTypes'] = $mimeTypes; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Caption|value-of $caption + */ + public function withCaption(Caption|int $caption): self + { + $self = clone $this; + $self['caption'] = $caption; + + return $self; + } + + /** + * Maximum caption length when captions are supported. + */ + public function withMaxCaptionLength(int $maxCaptionLength): self + { + $self = clone $this; + $self['maxCaptionLength'] = $maxCaptionLength; + + return $self; + } + + /** + * Maximum audio or video duration in seconds. + */ + public function withMaxDuration(int $maxDuration): self + { + $self = clone $this; + $self['maxDuration'] = $maxDuration; + + return $self; + } + + /** + * Maximum image or video height in pixels. + */ + public function withMaxHeight(int $maxHeight): self + { + $self = clone $this; + $self['maxHeight'] = $maxHeight; + + return $self; + } + + /** + * Maximum file size in bytes. + */ + public function withMaxSize(int $maxSize): self + { + $self = clone $this; + $self['maxSize'] = $maxSize; + + return $self; + } + + /** + * Maximum image or video width in pixels. + */ + public function withMaxWidth(int $maxWidth): self + { + $self = clone $this; + $self['maxWidth'] = $maxWidth; + + return $self; + } + + /** + * True if this file type can be sent as view-once media. + */ + public function withViewOnce(bool $viewOnce): self + { + $self = clone $this; + $self['viewOnce'] = $viewOnce; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/Attachment/Caption.php b/src/Chats/Chat/Capabilities/Attachment/Caption.php new file mode 100644 index 0000000..742fbf7 --- /dev/null +++ b/src/Chats/Chat/Capabilities/Attachment/Caption.php @@ -0,0 +1,21 @@ +|null, + * types?: list>|null, + * } + */ +final class DisappearingTimer implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * True if empty timer objects should be omitted from message content. + */ + #[Optional] + public ?bool $omitEmptyTimer; + + /** + * Allowed disappearing timer values in milliseconds. Omitted means any timer is allowed. + * + * @var list|null $timers + */ + #[Optional(list: 'int')] + public ?array $timers; + + /** + * Supported disappearing timer types. + * + * @var list>|null $types + */ + #[Optional(list: Type::class)] + public ?array $types; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param list|null $timers + * @param list>|null $types + */ + public static function with( + ?bool $omitEmptyTimer = null, + ?array $timers = null, + ?array $types = null + ): self { + $self = new self; + + null !== $omitEmptyTimer && $self['omitEmptyTimer'] = $omitEmptyTimer; + null !== $timers && $self['timers'] = $timers; + null !== $types && $self['types'] = $types; + + return $self; + } + + /** + * True if empty timer objects should be omitted from message content. + */ + public function withOmitEmptyTimer(bool $omitEmptyTimer): self + { + $self = clone $this; + $self['omitEmptyTimer'] = $omitEmptyTimer; + + return $self; + } + + /** + * Allowed disappearing timer values in milliseconds. Omitted means any timer is allowed. + * + * @param list $timers + */ + public function withTimers(array $timers): self + { + $self = clone $this; + $self['timers'] = $timers; + + return $self; + } + + /** + * Supported disappearing timer types. + * + * @param list> $types + */ + public function withTypes(array $types): self + { + $self = clone $this; + $self['types'] = $types; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/DisappearingTimer/Type.php b/src/Chats/Chat/Capabilities/DisappearingTimer/Type.php new file mode 100644 index 0000000..e606ae0 --- /dev/null +++ b/src/Chats/Chat/Capabilities/DisappearingTimer/Type.php @@ -0,0 +1,12 @@ +, + * acceptWithMessage?: null|AcceptWithMessage|value-of, + * } + */ +final class MessageRequest implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $acceptWithButton + */ + #[Optional(enum: AcceptWithButton::class)] + public ?int $acceptWithButton; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $acceptWithMessage + */ + #[Optional(enum: AcceptWithMessage::class)] + public ?int $acceptWithMessage; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param AcceptWithButton|value-of|null $acceptWithButton + * @param AcceptWithMessage|value-of|null $acceptWithMessage + */ + public static function with( + AcceptWithButton|int|null $acceptWithButton = null, + AcceptWithMessage|int|null $acceptWithMessage = null, + ): self { + $self = new self; + + null !== $acceptWithButton && $self['acceptWithButton'] = $acceptWithButton; + null !== $acceptWithMessage && $self['acceptWithMessage'] = $acceptWithMessage; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param AcceptWithButton|value-of $acceptWithButton + */ + public function withAcceptWithButton( + AcceptWithButton|int $acceptWithButton + ): self { + $self = clone $this; + $self['acceptWithButton'] = $acceptWithButton; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param AcceptWithMessage|value-of $acceptWithMessage + */ + public function withAcceptWithMessage( + AcceptWithMessage|int $acceptWithMessage + ): self { + $self = clone $this; + $self['acceptWithMessage'] = $acceptWithMessage; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/MessageRequest/AcceptWithButton.php b/src/Chats/Chat/Capabilities/MessageRequest/AcceptWithButton.php new file mode 100644 index 0000000..f612299 --- /dev/null +++ b/src/Chats/Chat/Capabilities/MessageRequest/AcceptWithButton.php @@ -0,0 +1,21 @@ +, + * invite?: null|Invite|value-of, + * kick?: null|Kick|value-of, + * leave?: null|Leave|value-of, + * revokeInvite?: null|RevokeInvite|value-of, + * } + */ +final class ParticipantActions implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $ban + */ + #[Optional(enum: Ban::class)] + public ?int $ban; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $invite + */ + #[Optional(enum: Invite::class)] + public ?int $invite; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $kick + */ + #[Optional(enum: Kick::class)] + public ?int $kick; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $leave + */ + #[Optional(enum: Leave::class)] + public ?int $leave; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of|null $revokeInvite + */ + #[Optional(enum: RevokeInvite::class)] + public ?int $revokeInvite; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Ban|value-of|null $ban + * @param Invite|value-of|null $invite + * @param Kick|value-of|null $kick + * @param Leave|value-of|null $leave + * @param RevokeInvite|value-of|null $revokeInvite + */ + public static function with( + Ban|int|null $ban = null, + Invite|int|null $invite = null, + Kick|int|null $kick = null, + Leave|int|null $leave = null, + RevokeInvite|int|null $revokeInvite = null, + ): self { + $self = new self; + + null !== $ban && $self['ban'] = $ban; + null !== $invite && $self['invite'] = $invite; + null !== $kick && $self['kick'] = $kick; + null !== $leave && $self['leave'] = $leave; + null !== $revokeInvite && $self['revokeInvite'] = $revokeInvite; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Ban|value-of $ban + */ + public function withBan(Ban|int $ban): self + { + $self = clone $this; + $self['ban'] = $ban; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Invite|value-of $invite + */ + public function withInvite(Invite|int $invite): self + { + $self = clone $this; + $self['invite'] = $invite; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Kick|value-of $kick + */ + public function withKick(Kick|int $kick): self + { + $self = clone $this; + $self['kick'] = $kick; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Leave|value-of $leave + */ + public function withLeave(Leave|int $leave): self + { + $self = clone $this; + $self['leave'] = $leave; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param RevokeInvite|value-of $revokeInvite + */ + public function withRevokeInvite(RevokeInvite|int $revokeInvite): self + { + $self = clone $this; + $self['revokeInvite'] = $revokeInvite; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/ParticipantActions/Ban.php b/src/Chats/Chat/Capabilities/ParticipantActions/Ban.php new file mode 100644 index 0000000..96d3af9 --- /dev/null +++ b/src/Chats/Chat/Capabilities/ParticipantActions/Ban.php @@ -0,0 +1,21 @@ + */ + use SdkModel; + + /** + * Chat avatar state capability. + */ + #[Optional] + public ?Avatar $avatar; + + /** + * Chat description/topic state capability. + */ + #[Optional] + public ?Description $description; + + /** + * Disappearing-message timer state capability. + */ + #[Optional] + public ?DisappearingTimer $disappearingTimer; + + /** + * Chat title state capability. + */ + #[Optional] + public ?Title $title; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Avatar|AvatarShape|null $avatar + * @param Description|DescriptionShape|null $description + * @param DisappearingTimer|DisappearingTimerShape|null $disappearingTimer + * @param Title|TitleShape|null $title + */ + public static function with( + Avatar|array|null $avatar = null, + Description|array|null $description = null, + DisappearingTimer|array|null $disappearingTimer = null, + Title|array|null $title = null, + ): self { + $self = new self; + + null !== $avatar && $self['avatar'] = $avatar; + null !== $description && $self['description'] = $description; + null !== $disappearingTimer && $self['disappearingTimer'] = $disappearingTimer; + null !== $title && $self['title'] = $title; + + return $self; + } + + /** + * Chat avatar state capability. + * + * @param Avatar|AvatarShape $avatar + */ + public function withAvatar(Avatar|array $avatar): self + { + $self = clone $this; + $self['avatar'] = $avatar; + + return $self; + } + + /** + * Chat description/topic state capability. + * + * @param Description|DescriptionShape $description + */ + public function withDescription(Description|array $description): self + { + $self = clone $this; + $self['description'] = $description; + + return $self; + } + + /** + * Disappearing-message timer state capability. + * + * @param DisappearingTimer|DisappearingTimerShape $disappearingTimer + */ + public function withDisappearingTimer( + DisappearingTimer|array $disappearingTimer, + ): self { + $self = clone $this; + $self['disappearingTimer'] = $disappearingTimer; + + return $self; + } + + /** + * Chat title state capability. + * + * @param Title|TitleShape $title + */ + public function withTitle(Title|array $title): self + { + $self = clone $this; + $self['title'] = $title; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/State/Avatar.php b/src/Chats/Chat/Capabilities/State/Avatar.php new file mode 100644 index 0000000..7c5571b --- /dev/null +++ b/src/Chats/Chat/Capabilities/State/Avatar.php @@ -0,0 +1,77 @@ +} + */ +final class Avatar implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of $level + */ + #[Required(enum: Level::class)] + public int $level; + + /** + * `new Avatar()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Avatar::with(level: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Avatar)->withLevel(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Level|value-of $level + */ + public static function with(Level|int $level): self + { + $self = new self; + + $self['level'] = $level; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Level|value-of $level + */ + public function withLevel(Level|int $level): self + { + $self = clone $this; + $self['level'] = $level; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/State/Avatar/Level.php b/src/Chats/Chat/Capabilities/State/Avatar/Level.php new file mode 100644 index 0000000..5af7747 --- /dev/null +++ b/src/Chats/Chat/Capabilities/State/Avatar/Level.php @@ -0,0 +1,21 @@ +} + */ +final class Description implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of $level + */ + #[Required(enum: Level::class)] + public int $level; + + /** + * `new Description()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Description::with(level: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Description)->withLevel(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Level|value-of $level + */ + public static function with(Level|int $level): self + { + $self = new self; + + $self['level'] = $level; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Level|value-of $level + */ + public function withLevel(Level|int $level): self + { + $self = clone $this; + $self['level'] = $level; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/State/Description/Level.php b/src/Chats/Chat/Capabilities/State/Description/Level.php new file mode 100644 index 0000000..b494808 --- /dev/null +++ b/src/Chats/Chat/Capabilities/State/Description/Level.php @@ -0,0 +1,21 @@ +} + */ +final class DisappearingTimer implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of $level + */ + #[Required(enum: Level::class)] + public int $level; + + /** + * `new DisappearingTimer()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * DisappearingTimer::with(level: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new DisappearingTimer)->withLevel(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Level|value-of $level + */ + public static function with(Level|int $level): self + { + $self = new self; + + $self['level'] = $level; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Level|value-of $level + */ + public function withLevel(Level|int $level): self + { + $self = clone $this; + $self['level'] = $level; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/State/DisappearingTimer/Level.php b/src/Chats/Chat/Capabilities/State/DisappearingTimer/Level.php new file mode 100644 index 0000000..96baa8a --- /dev/null +++ b/src/Chats/Chat/Capabilities/State/DisappearingTimer/Level.php @@ -0,0 +1,21 @@ +} + */ +final class Title implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @var value-of $level + */ + #[Required(enum: Level::class)] + public int $level; + + /** + * `new Title()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Title::with(level: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Title)->withLevel(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Level|value-of $level + */ + public static function with(Level|int $level): self + { + $self = new self; + + $self['level'] = $level; + + return $self; + } + + /** + * -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully supported. + * + * @param Level|value-of $level + */ + public function withLevel(Level|int $level): self + { + $self = clone $this; + $self['level'] = $level; + + return $self; + } +} diff --git a/src/Chats/Chat/Capabilities/State/Title/Level.php b/src/Chats/Chat/Capabilities/State/Title/Level.php new file mode 100644 index 0000000..1e1059e --- /dev/null +++ b/src/Chats/Chat/Capabilities/State/Title/Level.php @@ -0,0 +1,21 @@ +|null + * } + */ +final class Draft implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Matrix HTML draft body. + */ + #[Required] + public string $text; + + /** + * Draft attachments keyed by attachment ID. + * + * @var array|null $attachments + */ + #[Optional(map: Attachment::class)] + public ?array $attachments; + + /** + * `new Draft()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Draft::with(text: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Draft)->withText(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param array|null $attachments + */ + public static function with(string $text, ?array $attachments = null): self + { + $self = new self; + + $self['text'] = $text; + + null !== $attachments && $self['attachments'] = $attachments; + + return $self; + } + + /** + * Matrix HTML draft body. + */ + public function withText(string $text): self + { + $self = clone $this; + $self['text'] = $text; + + return $self; + } + + /** + * Draft attachments keyed by attachment ID. + * + * @param array $attachments + */ + public function withAttachments(array $attachments): self + { + $self = clone $this; + $self['attachments'] = $attachments; + + return $self; + } +} diff --git a/src/Chats/Chat/Draft/Attachment.php b/src/Chats/Chat/Draft/Attachment.php new file mode 100644 index 0000000..e525fec --- /dev/null +++ b/src/Chats/Chat/Draft/Attachment.php @@ -0,0 +1,246 @@ +, + * audioDurationSeconds?: float|null, + * fileName?: string|null, + * filePath?: string|null, + * fileSize?: float|null, + * mimeType?: string|null, + * size?: null|Size|SizeShape, + * stickerID?: string|null, + * } + */ +final class Attachment implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Draft attachment identifier. + */ + #[Required] + public string $id; + + /** + * Draft attachment type. GIF and recorded audio are mutually exclusive types. + * + * @var value-of $type + */ + #[Required(enum: Type::class)] + public string $type; + + /** + * Audio duration in seconds if known. + */ + #[Optional] + public ?float $audioDurationSeconds; + + /** + * Original filename if available. + */ + #[Optional] + public ?string $fileName; + + /** + * Local filesystem path for the draft attachment. + */ + #[Optional] + public ?string $filePath; + + /** + * File size in bytes if known. + */ + #[Optional] + public ?float $fileSize; + + /** + * MIME type if known. + */ + #[Optional] + public ?string $mimeType; + + /** + * Pixel dimensions of the attachment. + */ + #[Optional] + public ?Size $size; + + /** + * Sticker identifier if the draft attachment is a sticker. + */ + #[Optional] + public ?string $stickerID; + + /** + * `new Attachment()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Attachment::with(id: ..., type: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Attachment)->withID(...)->withType(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Type|value-of $type + * @param Size|SizeShape|null $size + */ + public static function with( + string $id, + Type|string $type, + ?float $audioDurationSeconds = null, + ?string $fileName = null, + ?string $filePath = null, + ?float $fileSize = null, + ?string $mimeType = null, + Size|array|null $size = null, + ?string $stickerID = null, + ): self { + $self = new self; + + $self['id'] = $id; + $self['type'] = $type; + + null !== $audioDurationSeconds && $self['audioDurationSeconds'] = $audioDurationSeconds; + null !== $fileName && $self['fileName'] = $fileName; + null !== $filePath && $self['filePath'] = $filePath; + null !== $fileSize && $self['fileSize'] = $fileSize; + null !== $mimeType && $self['mimeType'] = $mimeType; + null !== $size && $self['size'] = $size; + null !== $stickerID && $self['stickerID'] = $stickerID; + + return $self; + } + + /** + * Draft attachment identifier. + */ + public function withID(string $id): self + { + $self = clone $this; + $self['id'] = $id; + + return $self; + } + + /** + * Draft attachment type. GIF and recorded audio are mutually exclusive types. + * + * @param Type|value-of $type + */ + public function withType(Type|string $type): self + { + $self = clone $this; + $self['type'] = $type; + + return $self; + } + + /** + * Audio duration in seconds if known. + */ + public function withAudioDurationSeconds(float $audioDurationSeconds): self + { + $self = clone $this; + $self['audioDurationSeconds'] = $audioDurationSeconds; + + return $self; + } + + /** + * Original filename if available. + */ + public function withFileName(string $fileName): self + { + $self = clone $this; + $self['fileName'] = $fileName; + + return $self; + } + + /** + * Local filesystem path for the draft attachment. + */ + public function withFilePath(string $filePath): self + { + $self = clone $this; + $self['filePath'] = $filePath; + + return $self; + } + + /** + * File size in bytes if known. + */ + public function withFileSize(float $fileSize): self + { + $self = clone $this; + $self['fileSize'] = $fileSize; + + return $self; + } + + /** + * MIME type if known. + */ + public function withMimeType(string $mimeType): self + { + $self = clone $this; + $self['mimeType'] = $mimeType; + + return $self; + } + + /** + * Pixel dimensions of the attachment. + * + * @param Size|SizeShape $size + */ + public function withSize(Size|array $size): self + { + $self = clone $this; + $self['size'] = $size; + + return $self; + } + + /** + * Sticker identifier if the draft attachment is a sticker. + */ + public function withStickerID(string $stickerID): self + { + $self = clone $this; + $self['stickerID'] = $stickerID; + + return $self; + } +} diff --git a/src/Chats/Chat/Draft/Attachment/Size.php b/src/Chats/Chat/Draft/Attachment/Size.php new file mode 100644 index 0000000..a31a06a --- /dev/null +++ b/src/Chats/Chat/Draft/Attachment/Size.php @@ -0,0 +1,62 @@ + */ + use SdkModel; + + #[Optional] + public ?float $height; + + #[Optional] + public ?float $width; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with(?float $height = null, ?float $width = null): self + { + $self = new self; + + null !== $height && $self['height'] = $height; + null !== $width && $self['width'] = $width; + + return $self; + } + + public function withHeight(float $height): self + { + $self = clone $this; + $self['height'] = $height; + + return $self; + } + + public function withWidth(float $width): self + { + $self = clone $this; + $self['width'] = $width; + + return $self; + } +} diff --git a/src/Chats/Chat/Draft/Attachment/Type.php b/src/Chats/Chat/Draft/Attachment/Type.php new file mode 100644 index 0000000..e387fd8 --- /dev/null +++ b/src/Chats/Chat/Draft/Attachment/Type.php @@ -0,0 +1,17 @@ +, total: int + * hasMore: bool, items: list, total: int * } */ final class Participants implements BaseModel @@ -32,9 +32,9 @@ final class Participants implements BaseModel /** * Participants returned for this chat (limited by the request; may be a subset). * - * @var list $items + * @var list $items */ - #[Required(list: User::class)] + #[Required(list: Item::class)] public array $items; /** @@ -67,7 +67,7 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param list $items + * @param list $items */ public static function with(bool $hasMore, array $items, int $total): self { @@ -94,7 +94,7 @@ public function withHasMore(bool $hasMore): self /** * Participants returned for this chat (limited by the request; may be a subset). * - * @param list $items + * @param list $items */ public function withItems(array $items): self { diff --git a/src/Chats/Chat/Participants/Item.php b/src/Chats/Chat/Participants/Item.php new file mode 100644 index 0000000..5943db9 --- /dev/null +++ b/src/Chats/Chat/Participants/Item.php @@ -0,0 +1,275 @@ + */ + use SdkModel; + + /** + * Stable Beeper user ID. Use as the primary key when referencing a person. + */ + #[Required] + public string $id; + + /** + * True if Beeper cannot initiate messages to this user (e.g., blocked, network restriction, or no DM path). The user may still message you. + */ + #[Optional] + public ?bool $cannotMessage; + + /** + * Email address if known. Not guaranteed verified. + */ + #[Optional] + public ?string $email; + + /** + * Display name as shown in clients (e.g., 'Alice Example'). May include emojis. + */ + #[Optional] + public ?string $fullName; + + /** + * Avatar image URL if available. This may be a remote URL, Matrix media URL, data URL, or local filesystem URL depending on source and endpoint. May be temporary or local-only to this device; download promptly if durable access is needed. + */ + #[Optional] + public ?string $imgURL; + + /** + * True if this user represents the authenticated account's own identity. + */ + #[Optional] + public ?bool $isSelf; + + /** + * User's phone number in E.164 format (e.g., '+14155552671'). Omit if unknown. + */ + #[Optional] + public ?string $phoneNumber; + + /** + * Human-readable handle if available (e.g., '@alice'). May be network-specific and not globally unique. + */ + #[Optional] + public ?string $username; + + /** + * True if this participant has admin privileges in the chat. + */ + #[Optional] + public ?bool $isAdmin; + + /** + * True if this participant represents a network or bridge bot. + */ + #[Optional] + public ?bool $isNetworkBot; + + /** + * True if this participant has been invited but has not joined yet. + */ + #[Optional] + public ?bool $isPending; + + /** + * `new Item()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Item::with(id: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Item)->withID(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with( + string $id, + ?bool $cannotMessage = null, + ?string $email = null, + ?string $fullName = null, + ?string $imgURL = null, + ?bool $isSelf = null, + ?string $phoneNumber = null, + ?string $username = null, + ?bool $isAdmin = null, + ?bool $isNetworkBot = null, + ?bool $isPending = null, + ): self { + $self = new self; + + $self['id'] = $id; + + null !== $cannotMessage && $self['cannotMessage'] = $cannotMessage; + null !== $email && $self['email'] = $email; + null !== $fullName && $self['fullName'] = $fullName; + null !== $imgURL && $self['imgURL'] = $imgURL; + null !== $isSelf && $self['isSelf'] = $isSelf; + null !== $phoneNumber && $self['phoneNumber'] = $phoneNumber; + null !== $username && $self['username'] = $username; + null !== $isAdmin && $self['isAdmin'] = $isAdmin; + null !== $isNetworkBot && $self['isNetworkBot'] = $isNetworkBot; + null !== $isPending && $self['isPending'] = $isPending; + + return $self; + } + + /** + * Stable Beeper user ID. Use as the primary key when referencing a person. + */ + public function withID(string $id): self + { + $self = clone $this; + $self['id'] = $id; + + return $self; + } + + /** + * True if Beeper cannot initiate messages to this user (e.g., blocked, network restriction, or no DM path). The user may still message you. + */ + public function withCannotMessage(bool $cannotMessage): self + { + $self = clone $this; + $self['cannotMessage'] = $cannotMessage; + + return $self; + } + + /** + * Email address if known. Not guaranteed verified. + */ + public function withEmail(string $email): self + { + $self = clone $this; + $self['email'] = $email; + + return $self; + } + + /** + * Display name as shown in clients (e.g., 'Alice Example'). May include emojis. + */ + public function withFullName(string $fullName): self + { + $self = clone $this; + $self['fullName'] = $fullName; + + return $self; + } + + /** + * Avatar image URL if available. This may be a remote URL, Matrix media URL, data URL, or local filesystem URL depending on source and endpoint. May be temporary or local-only to this device; download promptly if durable access is needed. + */ + public function withImgURL(string $imgURL): self + { + $self = clone $this; + $self['imgURL'] = $imgURL; + + return $self; + } + + /** + * True if this user represents the authenticated account's own identity. + */ + public function withIsSelf(bool $isSelf): self + { + $self = clone $this; + $self['isSelf'] = $isSelf; + + return $self; + } + + /** + * User's phone number in E.164 format (e.g., '+14155552671'). Omit if unknown. + */ + public function withPhoneNumber(string $phoneNumber): self + { + $self = clone $this; + $self['phoneNumber'] = $phoneNumber; + + return $self; + } + + /** + * Human-readable handle if available (e.g., '@alice'). May be network-specific and not globally unique. + */ + public function withUsername(string $username): self + { + $self = clone $this; + $self['username'] = $username; + + return $self; + } + + /** + * True if this participant has admin privileges in the chat. + */ + public function withIsAdmin(bool $isAdmin): self + { + $self = clone $this; + $self['isAdmin'] = $isAdmin; + + return $self; + } + + /** + * True if this participant represents a network or bridge bot. + */ + public function withIsNetworkBot(bool $isNetworkBot): self + { + $self = clone $this; + $self['isNetworkBot'] = $isNetworkBot; + + return $self; + } + + /** + * True if this participant has been invited but has not joined yet. + */ + public function withIsPending(bool $isPending): self + { + $self = clone $this; + $self['isPending'] = $isPending; + + return $self; + } +} diff --git a/src/Chats/Chat/Reminder.php b/src/Chats/Chat/Reminder.php new file mode 100644 index 0000000..0808ea7 --- /dev/null +++ b/src/Chats/Chat/Reminder.php @@ -0,0 +1,79 @@ + */ + use SdkModel; + + /** + * Cancel reminder if someone messages in the chat. + */ + #[Optional] + public ?bool $dismissOnIncomingMessage; + + /** + * Timestamp when the reminder should trigger. + */ + #[Optional] + public ?\DateTimeInterface $remindAt; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with( + ?bool $dismissOnIncomingMessage = null, + ?\DateTimeInterface $remindAt = null + ): self { + $self = new self; + + null !== $dismissOnIncomingMessage && $self['dismissOnIncomingMessage'] = $dismissOnIncomingMessage; + null !== $remindAt && $self['remindAt'] = $remindAt; + + return $self; + } + + /** + * Cancel reminder if someone messages in the chat. + */ + public function withDismissOnIncomingMessage( + bool $dismissOnIncomingMessage + ): self { + $self = clone $this; + $self['dismissOnIncomingMessage'] = $dismissOnIncomingMessage; + + return $self; + } + + /** + * Timestamp when the reminder should trigger. + */ + public function withRemindAt(\DateTimeInterface $remindAt): self + { + $self = clone $this; + $self['remindAt'] = $remindAt; + + return $self; + } +} diff --git a/src/Chats/Chat/Snooze.php b/src/Chats/Chat/Snooze.php new file mode 100644 index 0000000..a002230 --- /dev/null +++ b/src/Chats/Chat/Snooze.php @@ -0,0 +1,78 @@ + */ + use SdkModel; + + /** + * Timestamp when the snooze expires. + */ + #[Optional] + public ?\DateTimeInterface $snoozeUntil; + + /** + * Timestamp when the user set the snooze. + */ + #[Optional] + public ?\DateTimeInterface $userSnoozedAt; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with( + ?\DateTimeInterface $snoozeUntil = null, + ?\DateTimeInterface $userSnoozedAt = null, + ): self { + $self = new self; + + null !== $snoozeUntil && $self['snoozeUntil'] = $snoozeUntil; + null !== $userSnoozedAt && $self['userSnoozedAt'] = $userSnoozedAt; + + return $self; + } + + /** + * Timestamp when the snooze expires. + */ + public function withSnoozeUntil(\DateTimeInterface $snoozeUntil): self + { + $self = clone $this; + $self['snoozeUntil'] = $snoozeUntil; + + return $self; + } + + /** + * Timestamp when the user set the snooze. + */ + public function withUserSnoozedAt(\DateTimeInterface $userSnoozedAt): self + { + $self = clone $this; + $self['userSnoozedAt'] = $userSnoozedAt; + + return $self; + } +} diff --git a/src/Chats/ChatCreateParams.php b/src/Chats/ChatCreateParams.php index 0bdb4bc..550038f 100644 --- a/src/Chats/ChatCreateParams.php +++ b/src/Chats/ChatCreateParams.php @@ -4,21 +4,24 @@ namespace BeeperDesktop\Chats; -use BeeperDesktop\Chats\ChatCreateParams\Chat; +use BeeperDesktop\Chats\ChatCreateParams\Type; +use BeeperDesktop\Core\Attributes\Optional; use BeeperDesktop\Core\Attributes\Required; use BeeperDesktop\Core\Concerns\SdkModel; use BeeperDesktop\Core\Concerns\SdkParams; use BeeperDesktop\Core\Contracts\BaseModel; /** - * Create a single/group chat (mode='create') or start a direct chat from merged user data (mode='start'). + * Create a direct or group chat from participant IDs. Returns the created chat. * * @see BeeperDesktop\Services\ChatsService::create() * - * @phpstan-import-type ChatShape from \BeeperDesktop\Chats\ChatCreateParams\Chat - * * @phpstan-type ChatCreateParamsShape = array{ - * chat: \BeeperDesktop\Chats\ChatCreateParams\Chat|ChatShape + * accountID: string, + * participantIDs: list, + * type: Type|value-of, + * messageText?: string|null, + * title?: string|null, * } */ final class ChatCreateParams implements BaseModel @@ -27,21 +30,55 @@ final class ChatCreateParams implements BaseModel use SdkModel; use SdkParams; + /** + * Account to create or start the chat on. + */ #[Required] - public Chat $chat; + public string $accountID; + + /** + * User IDs to include in the new chat. + * + * @var list $participantIDs + */ + #[Required(list: 'string')] + public array $participantIDs; + + /** + * 'single' requires exactly one participantID; 'group' supports multiple participants and optional title. + * + * @var value-of $type + */ + #[Required(enum: Type::class)] + public string $type; + + /** + * Optional first message content if the platform requires it to create the chat. + */ + #[Optional] + public ?string $messageText; + + /** + * Optional title for group chats; ignored for single chats on most networks. + */ + #[Optional] + public ?string $title; /** * `new ChatCreateParams()` is missing required properties by the API. * * To enforce required parameters use * ``` - * ChatCreateParams::with(chat: ...) + * ChatCreateParams::with(accountID: ..., participantIDs: ..., type: ...) * ``` * * Otherwise ensure the following setters are called * * ``` - * (new ChatCreateParams)->withChat(...) + * (new ChatCreateParams) + * ->withAccountID(...) + * ->withParticipantIDs(...) + * ->withType(...) * ``` */ public function __construct() @@ -54,26 +91,83 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * - * @param Chat|ChatShape $chat + * @param list $participantIDs + * @param Type|value-of $type */ public static function with( - Chat|array $chat + string $accountID, + array $participantIDs, + Type|string $type, + ?string $messageText = null, + ?string $title = null, ): self { $self = new self; - $self['chat'] = $chat; + $self['accountID'] = $accountID; + $self['participantIDs'] = $participantIDs; + $self['type'] = $type; + + null !== $messageText && $self['messageText'] = $messageText; + null !== $title && $self['title'] = $title; return $self; } /** - * @param Chat|ChatShape $chat + * Account to create or start the chat on. */ - public function withChat( - Chat|array $chat - ): self { + public function withAccountID(string $accountID): self + { + $self = clone $this; + $self['accountID'] = $accountID; + + return $self; + } + + /** + * User IDs to include in the new chat. + * + * @param list $participantIDs + */ + public function withParticipantIDs(array $participantIDs): self + { + $self = clone $this; + $self['participantIDs'] = $participantIDs; + + return $self; + } + + /** + * 'single' requires exactly one participantID; 'group' supports multiple participants and optional title. + * + * @param Type|value-of $type + */ + public function withType(Type|string $type): self + { + $self = clone $this; + $self['type'] = $type; + + return $self; + } + + /** + * Optional first message content if the platform requires it to create the chat. + */ + public function withMessageText(string $messageText): self + { + $self = clone $this; + $self['messageText'] = $messageText; + + return $self; + } + + /** + * Optional title for group chats; ignored for single chats on most networks. + */ + public function withTitle(string $title): self + { $self = clone $this; - $self['chat'] = $chat; + $self['title'] = $title; return $self; } diff --git a/src/Chats/ChatCreateParams/Chat.php b/src/Chats/ChatCreateParams/Chat.php deleted file mode 100644 index d1d63c2..0000000 --- a/src/Chats/ChatCreateParams/Chat.php +++ /dev/null @@ -1,237 +0,0 @@ -, - * participantIDs?: list|null, - * title?: string|null, - * type?: null|Type|value-of, - * user?: null|User|UserShape, - * } - */ -final class Chat implements BaseModel -{ - /** @use SdkModel */ - use SdkModel; - - /** - * Account to create or start the chat on. - */ - #[Required] - public string $accountID; - - /** - * Whether invite-based DM creation is allowed when required by the platform. Used for mode='start'. - */ - #[Optional] - public ?bool $allowInvite; - - /** - * Optional first message content if the platform requires it to create the chat. - */ - #[Optional] - public ?string $messageText; - - /** - * Operation mode. Defaults to 'create' when omitted. - * - * @var value-of|null $mode - */ - #[Optional(enum: Mode::class)] - public ?string $mode; - - /** - * Required when mode='create'. User IDs to include in the new chat. - * - * @var list|null $participantIDs - */ - #[Optional(list: 'string')] - public ?array $participantIDs; - - /** - * Optional title for group chats when mode='create'; ignored for single chats on most platforms. - */ - #[Optional] - public ?string $title; - - /** - * Required when mode='create'. 'single' requires exactly one participantID; 'group' supports multiple participants and optional title. - * - * @var value-of|null $type - */ - #[Optional(enum: Type::class)] - public ?string $type; - - /** - * Required when mode='start'. Merged user-like contact payload used to resolve the best identifier. - */ - #[Optional] - public ?User $user; - - /** - * `new Chat()` is missing required properties by the API. - * - * To enforce required parameters use - * ``` - * Chat::with(accountID: ...) - * ``` - * - * Otherwise ensure the following setters are called - * - * ``` - * (new Chat)->withAccountID(...) - * ``` - */ - public function __construct() - { - $this->initialize(); - } - - /** - * Construct an instance from the required parameters. - * - * You must use named parameters to construct any parameters with a default value. - * - * @param Mode|value-of|null $mode - * @param list|null $participantIDs - * @param Type|value-of|null $type - * @param User|UserShape|null $user - */ - public static function with( - string $accountID, - ?bool $allowInvite = null, - ?string $messageText = null, - Mode|string|null $mode = null, - ?array $participantIDs = null, - ?string $title = null, - Type|string|null $type = null, - User|array|null $user = null, - ): self { - $self = new self; - - $self['accountID'] = $accountID; - - null !== $allowInvite && $self['allowInvite'] = $allowInvite; - null !== $messageText && $self['messageText'] = $messageText; - null !== $mode && $self['mode'] = $mode; - null !== $participantIDs && $self['participantIDs'] = $participantIDs; - null !== $title && $self['title'] = $title; - null !== $type && $self['type'] = $type; - null !== $user && $self['user'] = $user; - - return $self; - } - - /** - * Account to create or start the chat on. - */ - public function withAccountID(string $accountID): self - { - $self = clone $this; - $self['accountID'] = $accountID; - - return $self; - } - - /** - * Whether invite-based DM creation is allowed when required by the platform. Used for mode='start'. - */ - public function withAllowInvite(bool $allowInvite): self - { - $self = clone $this; - $self['allowInvite'] = $allowInvite; - - return $self; - } - - /** - * Optional first message content if the platform requires it to create the chat. - */ - public function withMessageText(string $messageText): self - { - $self = clone $this; - $self['messageText'] = $messageText; - - return $self; - } - - /** - * Operation mode. Defaults to 'create' when omitted. - * - * @param Mode|value-of $mode - */ - public function withMode(Mode|string $mode): self - { - $self = clone $this; - $self['mode'] = $mode; - - return $self; - } - - /** - * Required when mode='create'. User IDs to include in the new chat. - * - * @param list $participantIDs - */ - public function withParticipantIDs(array $participantIDs): self - { - $self = clone $this; - $self['participantIDs'] = $participantIDs; - - return $self; - } - - /** - * Optional title for group chats when mode='create'; ignored for single chats on most platforms. - */ - public function withTitle(string $title): self - { - $self = clone $this; - $self['title'] = $title; - - return $self; - } - - /** - * Required when mode='create'. 'single' requires exactly one participantID; 'group' supports multiple participants and optional title. - * - * @param Type|value-of $type - */ - public function withType(Type|string $type): self - { - $self = clone $this; - $self['type'] = $type; - - return $self; - } - - /** - * Required when mode='start'. Merged user-like contact payload used to resolve the best identifier. - * - * @param User|UserShape $user - */ - public function withUser(User|array $user): self - { - $self = clone $this; - $self['user'] = $user; - - return $self; - } -} diff --git a/src/Chats/ChatCreateParams/Chat/Mode.php b/src/Chats/ChatCreateParams/Chat/Mode.php deleted file mode 100644 index ce0dabd..0000000 --- a/src/Chats/ChatCreateParams/Chat/Mode.php +++ /dev/null @@ -1,15 +0,0 @@ -, * unreadCount: int, + * capabilities?: null|Capabilities|CapabilitiesShape, + * description?: string|null, + * draft?: null|Draft|DraftShape, + * imgURL?: string|null, * isArchived?: bool|null, + * isLowPriority?: bool|null, + * isMarkedUnread?: bool|null, * isMuted?: bool|null, * isPinned?: bool|null, + * isReadOnly?: bool|null, * lastActivity?: \DateTimeInterface|null, * lastReadMessageSortKey?: string|null, * localChatID?: string|null, + * messageExpirySeconds?: int|null, + * reminder?: null|Reminder|ReminderShape, + * snooze?: null|Snooze|SnoozeShape, + * unreadMentionsCount?: int|null, * preview?: null|Message|MessageShape, * } */ @@ -49,6 +71,12 @@ final class ChatListResponse implements BaseModel #[Required] public string $accountID; + /** + * Display-only human-readable account/network name. + */ + #[Required] + public string $network; + /** * Chat participants information. */ @@ -75,12 +103,48 @@ final class ChatListResponse implements BaseModel #[Required] public int $unreadCount; + /** + * Chat capabilities reported by the platform. + */ + #[Optional] + public ?Capabilities $capabilities; + + /** + * Group chat description/topic when available. + */ + #[Optional(nullable: true)] + public ?string $description; + + /** + * Current draft object for this chat, or null when no draft is set. + */ + #[Optional(nullable: true)] + public ?Draft $draft; + + /** + * Local filesystem path to the chat avatar image when available. + */ + #[Optional(nullable: true)] + public ?string $imgURL; + /** * True if chat is archived. */ #[Optional] public ?bool $isArchived; + /** + * True if chat is marked low priority. + */ + #[Optional] + public ?bool $isLowPriority; + + /** + * True if the chat was explicitly marked unread by the authenticated user. + */ + #[Optional] + public ?bool $isMarkedUnread; + /** * True if chat notifications are muted. */ @@ -93,6 +157,12 @@ final class ChatListResponse implements BaseModel #[Optional] public ?bool $isPinned; + /** + * True if messages cannot be sent in this chat. + */ + #[Optional] + public ?bool $isReadOnly; + /** * Timestamp of last activity. */ @@ -111,6 +181,30 @@ final class ChatListResponse implements BaseModel #[Optional(nullable: true)] public ?string $localChatID; + /** + * Disappearing-message timer in seconds when available. + */ + #[Optional(nullable: true)] + public ?int $messageExpirySeconds; + + /** + * Current reminder for this chat, or null when no reminder is set. + */ + #[Optional(nullable: true)] + public ?Reminder $reminder; + + /** + * Current snooze state for this chat, or null when no snooze is set. + */ + #[Optional(nullable: true)] + public ?Snooze $snooze; + + /** + * Number of unread messages that mention the authenticated user or @room. + */ + #[Optional] + public ?int $unreadMentionsCount; + #[Optional] public ?Message $preview; @@ -122,6 +216,7 @@ final class ChatListResponse implements BaseModel * ChatListResponse::with( * id: ..., * accountID: ..., + * network: ..., * participants: ..., * title: ..., * type: ..., @@ -135,6 +230,7 @@ final class ChatListResponse implements BaseModel * (new ChatListResponse) * ->withID(...) * ->withAccountID(...) + * ->withNetwork(...) * ->withParticipants(...) * ->withTitle(...) * ->withType(...) @@ -153,38 +249,66 @@ public function __construct() * * @param Participants|ParticipantsShape $participants * @param Type|value-of $type + * @param Capabilities|CapabilitiesShape|null $capabilities + * @param Draft|DraftShape|null $draft + * @param Reminder|ReminderShape|null $reminder + * @param Snooze|SnoozeShape|null $snooze * @param Message|MessageShape|null $preview */ public static function with( string $id, string $accountID, + string $network, Participants|array $participants, string $title, Type|string $type, int $unreadCount, + Capabilities|array|null $capabilities = null, + ?string $description = null, + Draft|array|null $draft = null, + ?string $imgURL = null, ?bool $isArchived = null, + ?bool $isLowPriority = null, + ?bool $isMarkedUnread = null, ?bool $isMuted = null, ?bool $isPinned = null, + ?bool $isReadOnly = null, ?\DateTimeInterface $lastActivity = null, ?string $lastReadMessageSortKey = null, ?string $localChatID = null, + ?int $messageExpirySeconds = null, + Reminder|array|null $reminder = null, + Snooze|array|null $snooze = null, + ?int $unreadMentionsCount = null, Message|array|null $preview = null, ): self { $self = new self; $self['id'] = $id; $self['accountID'] = $accountID; + $self['network'] = $network; $self['participants'] = $participants; $self['title'] = $title; $self['type'] = $type; $self['unreadCount'] = $unreadCount; + null !== $capabilities && $self['capabilities'] = $capabilities; + null !== $description && $self['description'] = $description; + null !== $draft && $self['draft'] = $draft; + null !== $imgURL && $self['imgURL'] = $imgURL; null !== $isArchived && $self['isArchived'] = $isArchived; + null !== $isLowPriority && $self['isLowPriority'] = $isLowPriority; + null !== $isMarkedUnread && $self['isMarkedUnread'] = $isMarkedUnread; null !== $isMuted && $self['isMuted'] = $isMuted; null !== $isPinned && $self['isPinned'] = $isPinned; + null !== $isReadOnly && $self['isReadOnly'] = $isReadOnly; null !== $lastActivity && $self['lastActivity'] = $lastActivity; null !== $lastReadMessageSortKey && $self['lastReadMessageSortKey'] = $lastReadMessageSortKey; null !== $localChatID && $self['localChatID'] = $localChatID; + null !== $messageExpirySeconds && $self['messageExpirySeconds'] = $messageExpirySeconds; + null !== $reminder && $self['reminder'] = $reminder; + null !== $snooze && $self['snooze'] = $snooze; + null !== $unreadMentionsCount && $self['unreadMentionsCount'] = $unreadMentionsCount; null !== $preview && $self['preview'] = $preview; return $self; @@ -212,6 +336,17 @@ public function withAccountID(string $accountID): self return $self; } + /** + * Display-only human-readable account/network name. + */ + public function withNetwork(string $network): self + { + $self = clone $this; + $self['network'] = $network; + + return $self; + } + /** * Chat participants information. * @@ -260,6 +395,54 @@ public function withUnreadCount(int $unreadCount): self return $self; } + /** + * Chat capabilities reported by the platform. + * + * @param Capabilities|CapabilitiesShape $capabilities + */ + public function withCapabilities(Capabilities|array $capabilities): self + { + $self = clone $this; + $self['capabilities'] = $capabilities; + + return $self; + } + + /** + * Group chat description/topic when available. + */ + public function withDescription(?string $description): self + { + $self = clone $this; + $self['description'] = $description; + + return $self; + } + + /** + * Current draft object for this chat, or null when no draft is set. + * + * @param Draft|DraftShape|null $draft + */ + public function withDraft(Draft|array|null $draft): self + { + $self = clone $this; + $self['draft'] = $draft; + + return $self; + } + + /** + * Local filesystem path to the chat avatar image when available. + */ + public function withImgURL(?string $imgURL): self + { + $self = clone $this; + $self['imgURL'] = $imgURL; + + return $self; + } + /** * True if chat is archived. */ @@ -271,6 +454,28 @@ public function withIsArchived(bool $isArchived): self return $self; } + /** + * True if chat is marked low priority. + */ + public function withIsLowPriority(bool $isLowPriority): self + { + $self = clone $this; + $self['isLowPriority'] = $isLowPriority; + + return $self; + } + + /** + * True if the chat was explicitly marked unread by the authenticated user. + */ + public function withIsMarkedUnread(bool $isMarkedUnread): self + { + $self = clone $this; + $self['isMarkedUnread'] = $isMarkedUnread; + + return $self; + } + /** * True if chat notifications are muted. */ @@ -293,6 +498,17 @@ public function withIsPinned(bool $isPinned): self return $self; } + /** + * True if messages cannot be sent in this chat. + */ + public function withIsReadOnly(bool $isReadOnly): self + { + $self = clone $this; + $self['isReadOnly'] = $isReadOnly; + + return $self; + } + /** * Timestamp of last activity. */ @@ -327,6 +543,54 @@ public function withLocalChatID(?string $localChatID): self return $self; } + /** + * Disappearing-message timer in seconds when available. + */ + public function withMessageExpirySeconds(?int $messageExpirySeconds): self + { + $self = clone $this; + $self['messageExpirySeconds'] = $messageExpirySeconds; + + return $self; + } + + /** + * Current reminder for this chat, or null when no reminder is set. + * + * @param Reminder|ReminderShape|null $reminder + */ + public function withReminder(Reminder|array|null $reminder): self + { + $self = clone $this; + $self['reminder'] = $reminder; + + return $self; + } + + /** + * Current snooze state for this chat, or null when no snooze is set. + * + * @param Snooze|SnoozeShape|null $snooze + */ + public function withSnooze(Snooze|array|null $snooze): self + { + $self = clone $this; + $self['snooze'] = $snooze; + + return $self; + } + + /** + * Number of unread messages that mention the authenticated user or @room. + */ + public function withUnreadMentionsCount(int $unreadMentionsCount): self + { + $self = clone $this; + $self['unreadMentionsCount'] = $unreadMentionsCount; + + return $self; + } + /** * @param Message|MessageShape $preview */ diff --git a/src/Chats/ChatMarkReadParams.php b/src/Chats/ChatMarkReadParams.php new file mode 100644 index 0000000..882e5ec --- /dev/null +++ b/src/Chats/ChatMarkReadParams.php @@ -0,0 +1,60 @@ + */ + use SdkModel; + use SdkParams; + + /** + * Optional message ID to mark read through. + */ + #[Optional] + public ?string $messageID; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with(?string $messageID = null): self + { + $self = new self; + + null !== $messageID && $self['messageID'] = $messageID; + + return $self; + } + + /** + * Optional message ID to mark read through. + */ + public function withMessageID(string $messageID): self + { + $self = clone $this; + $self['messageID'] = $messageID; + + return $self; + } +} diff --git a/src/Chats/ChatMarkUnreadParams.php b/src/Chats/ChatMarkUnreadParams.php new file mode 100644 index 0000000..0167a3d --- /dev/null +++ b/src/Chats/ChatMarkUnreadParams.php @@ -0,0 +1,60 @@ + */ + use SdkModel; + use SdkParams; + + /** + * Optional message ID to mark unread from. + */ + #[Optional] + public ?string $messageID; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with(?string $messageID = null): self + { + $self = new self; + + null !== $messageID && $self['messageID'] = $messageID; + + return $self; + } + + /** + * Optional message ID to mark unread from. + */ + public function withMessageID(string $messageID): self + { + $self = clone $this; + $self['messageID'] = $messageID; + + return $self; + } +} diff --git a/src/Chats/ChatNewResponse.php b/src/Chats/ChatNewResponse.php index 23d00d4..39b9919 100644 --- a/src/Chats/ChatNewResponse.php +++ b/src/Chats/ChatNewResponse.php @@ -4,6 +4,12 @@ namespace BeeperDesktop\Chats; +use BeeperDesktop\Chats\Chat\Capabilities; +use BeeperDesktop\Chats\Chat\Draft; +use BeeperDesktop\Chats\Chat\Participants; +use BeeperDesktop\Chats\Chat\Reminder; +use BeeperDesktop\Chats\Chat\Snooze; +use BeeperDesktop\Chats\Chat\Type; use BeeperDesktop\Chats\ChatNewResponse\Status; use BeeperDesktop\Core\Attributes\Optional; use BeeperDesktop\Core\Attributes\Required; @@ -11,8 +17,39 @@ use BeeperDesktop\Core\Contracts\BaseModel; /** + * @phpstan-import-type ParticipantsShape from \BeeperDesktop\Chats\Chat\Participants + * @phpstan-import-type CapabilitiesShape from \BeeperDesktop\Chats\Chat\Capabilities + * @phpstan-import-type DraftShape from \BeeperDesktop\Chats\Chat\Draft + * @phpstan-import-type ReminderShape from \BeeperDesktop\Chats\Chat\Reminder + * @phpstan-import-type SnoozeShape from \BeeperDesktop\Chats\Chat\Snooze + * * @phpstan-type ChatNewResponseShape = array{ - * chatID: string, status?: null|Status|value-of + * id: string, + * accountID: string, + * network: string, + * participants: Participants|ParticipantsShape, + * title: string, + * type: Type|value-of, + * unreadCount: int, + * capabilities?: null|Capabilities|CapabilitiesShape, + * description?: string|null, + * draft?: null|Draft|DraftShape, + * imgURL?: string|null, + * isArchived?: bool|null, + * isLowPriority?: bool|null, + * isMarkedUnread?: bool|null, + * isMuted?: bool|null, + * isPinned?: bool|null, + * isReadOnly?: bool|null, + * lastActivity?: \DateTimeInterface|null, + * lastReadMessageSortKey?: string|null, + * localChatID?: string|null, + * messageExpirySeconds?: int|null, + * reminder?: null|Reminder|ReminderShape, + * snooze?: null|Snooze|SnoozeShape, + * unreadMentionsCount?: int|null, + * chatID: string, + * status?: null|Status|value-of, * } */ final class ChatNewResponse implements BaseModel @@ -21,13 +58,163 @@ final class ChatNewResponse implements BaseModel use SdkModel; /** - * Newly created chat ID. + * Unique identifier of the chat across Beeper. + */ + #[Required] + public string $id; + + /** + * Account ID this chat belongs to. + */ + #[Required] + public string $accountID; + + /** + * Display-only human-readable account/network name. + */ + #[Required] + public string $network; + + /** + * Chat participants information. + */ + #[Required] + public Participants $participants; + + /** + * Display title of the chat as computed by the client/server. + */ + #[Required] + public string $title; + + /** + * Chat type: 'single' for direct messages, 'group' for group chats. + * + * @var value-of $type + */ + #[Required(enum: Type::class)] + public string $type; + + /** + * Number of unread messages. + */ + #[Required] + public int $unreadCount; + + /** + * Chat capabilities reported by the platform. + */ + #[Optional] + public ?Capabilities $capabilities; + + /** + * Group chat description/topic when available. + */ + #[Optional(nullable: true)] + public ?string $description; + + /** + * Current draft object for this chat, or null when no draft is set. + */ + #[Optional(nullable: true)] + public ?Draft $draft; + + /** + * Local filesystem path to the chat avatar image when available. + */ + #[Optional(nullable: true)] + public ?string $imgURL; + + /** + * True if chat is archived. + */ + #[Optional] + public ?bool $isArchived; + + /** + * True if chat is marked low priority. + */ + #[Optional] + public ?bool $isLowPriority; + + /** + * True if the chat was explicitly marked unread by the authenticated user. + */ + #[Optional] + public ?bool $isMarkedUnread; + + /** + * True if chat notifications are muted. + */ + #[Optional] + public ?bool $isMuted; + + /** + * True if chat is pinned. + */ + #[Optional] + public ?bool $isPinned; + + /** + * True if messages cannot be sent in this chat. + */ + #[Optional] + public ?bool $isReadOnly; + + /** + * Timestamp of last activity. + */ + #[Optional] + public ?\DateTimeInterface $lastActivity; + + /** + * Last read message sortKey. + */ + #[Optional] + public ?string $lastReadMessageSortKey; + + /** + * Local chat ID specific to this Beeper Desktop installation. + */ + #[Optional(nullable: true)] + public ?string $localChatID; + + /** + * Disappearing-message timer in seconds when available. + */ + #[Optional(nullable: true)] + public ?int $messageExpirySeconds; + + /** + * Current reminder for this chat, or null when no reminder is set. + */ + #[Optional(nullable: true)] + public ?Reminder $reminder; + + /** + * Current snooze state for this chat, or null when no snooze is set. + */ + #[Optional(nullable: true)] + public ?Snooze $snooze; + + /** + * Number of unread messages that mention the authenticated user or @room. + */ + #[Optional] + public ?int $unreadMentionsCount; + + /** + * @deprecated + * + * DEPRECATED - use id instead. Compatibility alias for older clients. */ #[Required] public string $chatID; /** - * Only returned in start mode. 'existing' means an existing chat was reused; 'created' means a new chat was created. + * @deprecated + * + * DEPRECATED - legacy start-chat status for older clients. New clients should inspect the returned Chat instead. * * @var value-of|null $status */ @@ -39,13 +226,30 @@ final class ChatNewResponse implements BaseModel * * To enforce required parameters use * ``` - * ChatNewResponse::with(chatID: ...) + * ChatNewResponse::with( + * id: ..., + * accountID: ..., + * network: ..., + * participants: ..., + * title: ..., + * type: ..., + * unreadCount: ..., + * chatID: ..., + * ) * ``` * * Otherwise ensure the following setters are called * * ``` - * (new ChatNewResponse)->withChatID(...) + * (new ChatNewResponse) + * ->withID(...) + * ->withAccountID(...) + * ->withNetwork(...) + * ->withParticipants(...) + * ->withTitle(...) + * ->withType(...) + * ->withUnreadCount(...) + * ->withChatID(...) * ``` */ public function __construct() @@ -58,23 +262,354 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. * + * @param Participants|ParticipantsShape $participants + * @param Type|value-of $type + * @param Capabilities|CapabilitiesShape|null $capabilities + * @param Draft|DraftShape|null $draft + * @param Reminder|ReminderShape|null $reminder + * @param Snooze|SnoozeShape|null $snooze * @param Status|value-of|null $status */ public static function with( + string $id, + string $accountID, + string $network, + Participants|array $participants, + string $title, + Type|string $type, + int $unreadCount, string $chatID, - Status|string|null $status = null + Capabilities|array|null $capabilities = null, + ?string $description = null, + Draft|array|null $draft = null, + ?string $imgURL = null, + ?bool $isArchived = null, + ?bool $isLowPriority = null, + ?bool $isMarkedUnread = null, + ?bool $isMuted = null, + ?bool $isPinned = null, + ?bool $isReadOnly = null, + ?\DateTimeInterface $lastActivity = null, + ?string $lastReadMessageSortKey = null, + ?string $localChatID = null, + ?int $messageExpirySeconds = null, + Reminder|array|null $reminder = null, + Snooze|array|null $snooze = null, + ?int $unreadMentionsCount = null, + Status|string|null $status = null, ): self { $self = new self; + $self['id'] = $id; + $self['accountID'] = $accountID; + $self['network'] = $network; + $self['participants'] = $participants; + $self['title'] = $title; + $self['type'] = $type; + $self['unreadCount'] = $unreadCount; $self['chatID'] = $chatID; + null !== $capabilities && $self['capabilities'] = $capabilities; + null !== $description && $self['description'] = $description; + null !== $draft && $self['draft'] = $draft; + null !== $imgURL && $self['imgURL'] = $imgURL; + null !== $isArchived && $self['isArchived'] = $isArchived; + null !== $isLowPriority && $self['isLowPriority'] = $isLowPriority; + null !== $isMarkedUnread && $self['isMarkedUnread'] = $isMarkedUnread; + null !== $isMuted && $self['isMuted'] = $isMuted; + null !== $isPinned && $self['isPinned'] = $isPinned; + null !== $isReadOnly && $self['isReadOnly'] = $isReadOnly; + null !== $lastActivity && $self['lastActivity'] = $lastActivity; + null !== $lastReadMessageSortKey && $self['lastReadMessageSortKey'] = $lastReadMessageSortKey; + null !== $localChatID && $self['localChatID'] = $localChatID; + null !== $messageExpirySeconds && $self['messageExpirySeconds'] = $messageExpirySeconds; + null !== $reminder && $self['reminder'] = $reminder; + null !== $snooze && $self['snooze'] = $snooze; + null !== $unreadMentionsCount && $self['unreadMentionsCount'] = $unreadMentionsCount; null !== $status && $self['status'] = $status; return $self; } /** - * Newly created chat ID. + * Unique identifier of the chat across Beeper. + */ + public function withID(string $id): self + { + $self = clone $this; + $self['id'] = $id; + + return $self; + } + + /** + * Account ID this chat belongs to. + */ + public function withAccountID(string $accountID): self + { + $self = clone $this; + $self['accountID'] = $accountID; + + return $self; + } + + /** + * Display-only human-readable account/network name. + */ + public function withNetwork(string $network): self + { + $self = clone $this; + $self['network'] = $network; + + return $self; + } + + /** + * Chat participants information. + * + * @param Participants|ParticipantsShape $participants + */ + public function withParticipants(Participants|array $participants): self + { + $self = clone $this; + $self['participants'] = $participants; + + return $self; + } + + /** + * Display title of the chat as computed by the client/server. + */ + public function withTitle(string $title): self + { + $self = clone $this; + $self['title'] = $title; + + return $self; + } + + /** + * Chat type: 'single' for direct messages, 'group' for group chats. + * + * @param Type|value-of $type + */ + public function withType(Type|string $type): self + { + $self = clone $this; + $self['type'] = $type; + + return $self; + } + + /** + * Number of unread messages. + */ + public function withUnreadCount(int $unreadCount): self + { + $self = clone $this; + $self['unreadCount'] = $unreadCount; + + return $self; + } + + /** + * Chat capabilities reported by the platform. + * + * @param Capabilities|CapabilitiesShape $capabilities + */ + public function withCapabilities(Capabilities|array $capabilities): self + { + $self = clone $this; + $self['capabilities'] = $capabilities; + + return $self; + } + + /** + * Group chat description/topic when available. + */ + public function withDescription(?string $description): self + { + $self = clone $this; + $self['description'] = $description; + + return $self; + } + + /** + * Current draft object for this chat, or null when no draft is set. + * + * @param Draft|DraftShape|null $draft + */ + public function withDraft(Draft|array|null $draft): self + { + $self = clone $this; + $self['draft'] = $draft; + + return $self; + } + + /** + * Local filesystem path to the chat avatar image when available. + */ + public function withImgURL(?string $imgURL): self + { + $self = clone $this; + $self['imgURL'] = $imgURL; + + return $self; + } + + /** + * True if chat is archived. + */ + public function withIsArchived(bool $isArchived): self + { + $self = clone $this; + $self['isArchived'] = $isArchived; + + return $self; + } + + /** + * True if chat is marked low priority. + */ + public function withIsLowPriority(bool $isLowPriority): self + { + $self = clone $this; + $self['isLowPriority'] = $isLowPriority; + + return $self; + } + + /** + * True if the chat was explicitly marked unread by the authenticated user. + */ + public function withIsMarkedUnread(bool $isMarkedUnread): self + { + $self = clone $this; + $self['isMarkedUnread'] = $isMarkedUnread; + + return $self; + } + + /** + * True if chat notifications are muted. + */ + public function withIsMuted(bool $isMuted): self + { + $self = clone $this; + $self['isMuted'] = $isMuted; + + return $self; + } + + /** + * True if chat is pinned. + */ + public function withIsPinned(bool $isPinned): self + { + $self = clone $this; + $self['isPinned'] = $isPinned; + + return $self; + } + + /** + * True if messages cannot be sent in this chat. + */ + public function withIsReadOnly(bool $isReadOnly): self + { + $self = clone $this; + $self['isReadOnly'] = $isReadOnly; + + return $self; + } + + /** + * Timestamp of last activity. + */ + public function withLastActivity(\DateTimeInterface $lastActivity): self + { + $self = clone $this; + $self['lastActivity'] = $lastActivity; + + return $self; + } + + /** + * Last read message sortKey. + */ + public function withLastReadMessageSortKey( + string $lastReadMessageSortKey + ): self { + $self = clone $this; + $self['lastReadMessageSortKey'] = $lastReadMessageSortKey; + + return $self; + } + + /** + * Local chat ID specific to this Beeper Desktop installation. + */ + public function withLocalChatID(?string $localChatID): self + { + $self = clone $this; + $self['localChatID'] = $localChatID; + + return $self; + } + + /** + * Disappearing-message timer in seconds when available. + */ + public function withMessageExpirySeconds(?int $messageExpirySeconds): self + { + $self = clone $this; + $self['messageExpirySeconds'] = $messageExpirySeconds; + + return $self; + } + + /** + * Current reminder for this chat, or null when no reminder is set. + * + * @param Reminder|ReminderShape|null $reminder + */ + public function withReminder(Reminder|array|null $reminder): self + { + $self = clone $this; + $self['reminder'] = $reminder; + + return $self; + } + + /** + * Current snooze state for this chat, or null when no snooze is set. + * + * @param Snooze|SnoozeShape|null $snooze + */ + public function withSnooze(Snooze|array|null $snooze): self + { + $self = clone $this; + $self['snooze'] = $snooze; + + return $self; + } + + /** + * Number of unread messages that mention the authenticated user or @room. + */ + public function withUnreadMentionsCount(int $unreadMentionsCount): self + { + $self = clone $this; + $self['unreadMentionsCount'] = $unreadMentionsCount; + + return $self; + } + + /** + * DEPRECATED - use id instead. Compatibility alias for older clients. */ public function withChatID(string $chatID): self { @@ -85,7 +620,7 @@ public function withChatID(string $chatID): self } /** - * Only returned in start mode. 'existing' means an existing chat was reused; 'created' means a new chat was created. + * DEPRECATED - legacy start-chat status for older clients. New clients should inspect the returned Chat instead. * * @param Status|value-of $status */ diff --git a/src/Chats/ChatNewResponse/Status.php b/src/Chats/ChatNewResponse/Status.php index 64fd131..8ecda0f 100644 --- a/src/Chats/ChatNewResponse/Status.php +++ b/src/Chats/ChatNewResponse/Status.php @@ -5,7 +5,9 @@ namespace BeeperDesktop\Chats\ChatNewResponse; /** - * Only returned in start mode. 'existing' means an existing chat was reused; 'created' means a new chat was created. + * DEPRECATED - legacy start-chat status for older clients. New clients should inspect the returned Chat instead. + * + * @deprecated */ enum Status: string { diff --git a/src/Chats/ChatRetrieveParams.php b/src/Chats/ChatRetrieveParams.php index c67f16c..09968b7 100644 --- a/src/Chats/ChatRetrieveParams.php +++ b/src/Chats/ChatRetrieveParams.php @@ -23,7 +23,7 @@ final class ChatRetrieveParams implements BaseModel use SdkParams; /** - * Maximum number of participants to return. Use -1 for all; otherwise 0–500. Defaults to all (-1). + * Maximum number of participants to return. Use -1 for all; otherwise 0-500. Defaults to 100. List and search endpoints return up to 20 participants per chat. */ #[Optional(nullable: true)] public ?int $maxParticipantCount; @@ -48,7 +48,7 @@ public static function with(?int $maxParticipantCount = null): self } /** - * Maximum number of participants to return. Use -1 for all; otherwise 0–500. Defaults to all (-1). + * Maximum number of participants to return. Use -1 for all; otherwise 0-500. Defaults to 100. List and search endpoints return up to 20 participants per chat. */ public function withMaxParticipantCount(?int $maxParticipantCount): self { diff --git a/src/Chats/ChatSearchParams.php b/src/Chats/ChatSearchParams.php index 226d9f1..569c125 100644 --- a/src/Chats/ChatSearchParams.php +++ b/src/Chats/ChatSearchParams.php @@ -14,7 +14,7 @@ use BeeperDesktop\Core\Contracts\BaseModel; /** - * Search chats by title/network or participants using Beeper Desktop's renderer algorithm. + * Search chats by title, network, or participant names. * * @see BeeperDesktop\Services\ChatsService::search() * diff --git a/src/Chats/ChatStartParams.php b/src/Chats/ChatStartParams.php new file mode 100644 index 0000000..57fa5ab --- /dev/null +++ b/src/Chats/ChatStartParams.php @@ -0,0 +1,146 @@ + */ + use SdkModel; + use SdkParams; + + /** + * Account to create or start the chat on. + */ + #[Required] + public string $accountID; + + /** + * Merged user-like contact payload used to resolve the best identifier. + */ + #[Required] + public User $user; + + /** + * Whether invite-based DM creation is allowed when required by the platform. + */ + #[Optional] + public ?bool $allowInvite; + + /** + * Optional first message content if the platform requires it to create the chat. + */ + #[Optional] + public ?string $messageText; + + /** + * `new ChatStartParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * ChatStartParams::with(accountID: ..., user: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new ChatStartParams)->withAccountID(...)->withUser(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param User|UserShape $user + */ + public static function with( + string $accountID, + User|array $user, + ?bool $allowInvite = null, + ?string $messageText = null, + ): self { + $self = new self; + + $self['accountID'] = $accountID; + $self['user'] = $user; + + null !== $allowInvite && $self['allowInvite'] = $allowInvite; + null !== $messageText && $self['messageText'] = $messageText; + + return $self; + } + + /** + * Account to create or start the chat on. + */ + public function withAccountID(string $accountID): self + { + $self = clone $this; + $self['accountID'] = $accountID; + + return $self; + } + + /** + * Merged user-like contact payload used to resolve the best identifier. + * + * @param User|UserShape $user + */ + public function withUser(User|array $user): self + { + $self = clone $this; + $self['user'] = $user; + + return $self; + } + + /** + * Whether invite-based DM creation is allowed when required by the platform. + */ + public function withAllowInvite(bool $allowInvite): self + { + $self = clone $this; + $self['allowInvite'] = $allowInvite; + + return $self; + } + + /** + * Optional first message content if the platform requires it to create the chat. + */ + public function withMessageText(string $messageText): self + { + $self = clone $this; + $self['messageText'] = $messageText; + + return $self; + } +} diff --git a/src/Chats/ChatCreateParams/Chat/User.php b/src/Chats/ChatStartParams/User.php similarity index 94% rename from src/Chats/ChatCreateParams/Chat/User.php rename to src/Chats/ChatStartParams/User.php index df629a7..fdd9f55 100644 --- a/src/Chats/ChatCreateParams/Chat/User.php +++ b/src/Chats/ChatStartParams/User.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace BeeperDesktop\Chats\ChatCreateParams\Chat; +namespace BeeperDesktop\Chats\ChatStartParams; use BeeperDesktop\Core\Attributes\Optional; use BeeperDesktop\Core\Concerns\SdkModel; use BeeperDesktop\Core\Contracts\BaseModel; /** - * Required when mode='start'. Merged user-like contact payload used to resolve the best identifier. + * Merged user-like contact payload used to resolve the best identifier. * * @phpstan-type UserShape = array{ * id?: string|null, diff --git a/src/Chats/ChatStartResponse.php b/src/Chats/ChatStartResponse.php new file mode 100644 index 0000000..ce14fdf --- /dev/null +++ b/src/Chats/ChatStartResponse.php @@ -0,0 +1,634 @@ +, + * unreadCount: int, + * capabilities?: null|Capabilities|CapabilitiesShape, + * description?: string|null, + * draft?: null|Draft|DraftShape, + * imgURL?: string|null, + * isArchived?: bool|null, + * isLowPriority?: bool|null, + * isMarkedUnread?: bool|null, + * isMuted?: bool|null, + * isPinned?: bool|null, + * isReadOnly?: bool|null, + * lastActivity?: \DateTimeInterface|null, + * lastReadMessageSortKey?: string|null, + * localChatID?: string|null, + * messageExpirySeconds?: int|null, + * reminder?: null|Reminder|ReminderShape, + * snooze?: null|Snooze|SnoozeShape, + * unreadMentionsCount?: int|null, + * chatID: string, + * status?: null|Status|value-of, + * } + */ +final class ChatStartResponse implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Unique identifier of the chat across Beeper. + */ + #[Required] + public string $id; + + /** + * Account ID this chat belongs to. + */ + #[Required] + public string $accountID; + + /** + * Display-only human-readable account/network name. + */ + #[Required] + public string $network; + + /** + * Chat participants information. + */ + #[Required] + public Participants $participants; + + /** + * Display title of the chat as computed by the client/server. + */ + #[Required] + public string $title; + + /** + * Chat type: 'single' for direct messages, 'group' for group chats. + * + * @var value-of $type + */ + #[Required(enum: Type::class)] + public string $type; + + /** + * Number of unread messages. + */ + #[Required] + public int $unreadCount; + + /** + * Chat capabilities reported by the platform. + */ + #[Optional] + public ?Capabilities $capabilities; + + /** + * Group chat description/topic when available. + */ + #[Optional(nullable: true)] + public ?string $description; + + /** + * Current draft object for this chat, or null when no draft is set. + */ + #[Optional(nullable: true)] + public ?Draft $draft; + + /** + * Local filesystem path to the chat avatar image when available. + */ + #[Optional(nullable: true)] + public ?string $imgURL; + + /** + * True if chat is archived. + */ + #[Optional] + public ?bool $isArchived; + + /** + * True if chat is marked low priority. + */ + #[Optional] + public ?bool $isLowPriority; + + /** + * True if the chat was explicitly marked unread by the authenticated user. + */ + #[Optional] + public ?bool $isMarkedUnread; + + /** + * True if chat notifications are muted. + */ + #[Optional] + public ?bool $isMuted; + + /** + * True if chat is pinned. + */ + #[Optional] + public ?bool $isPinned; + + /** + * True if messages cannot be sent in this chat. + */ + #[Optional] + public ?bool $isReadOnly; + + /** + * Timestamp of last activity. + */ + #[Optional] + public ?\DateTimeInterface $lastActivity; + + /** + * Last read message sortKey. + */ + #[Optional] + public ?string $lastReadMessageSortKey; + + /** + * Local chat ID specific to this Beeper Desktop installation. + */ + #[Optional(nullable: true)] + public ?string $localChatID; + + /** + * Disappearing-message timer in seconds when available. + */ + #[Optional(nullable: true)] + public ?int $messageExpirySeconds; + + /** + * Current reminder for this chat, or null when no reminder is set. + */ + #[Optional(nullable: true)] + public ?Reminder $reminder; + + /** + * Current snooze state for this chat, or null when no snooze is set. + */ + #[Optional(nullable: true)] + public ?Snooze $snooze; + + /** + * Number of unread messages that mention the authenticated user or @room. + */ + #[Optional] + public ?int $unreadMentionsCount; + + /** + * @deprecated + * + * DEPRECATED - use id instead. Compatibility alias for older clients. + */ + #[Required] + public string $chatID; + + /** + * @deprecated + * + * DEPRECATED - legacy start-chat status for older clients. New clients should inspect the returned Chat instead. + * + * @var value-of|null $status + */ + #[Optional(enum: Status::class)] + public ?string $status; + + /** + * `new ChatStartResponse()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * ChatStartResponse::with( + * id: ..., + * accountID: ..., + * network: ..., + * participants: ..., + * title: ..., + * type: ..., + * unreadCount: ..., + * chatID: ..., + * ) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new ChatStartResponse) + * ->withID(...) + * ->withAccountID(...) + * ->withNetwork(...) + * ->withParticipants(...) + * ->withTitle(...) + * ->withType(...) + * ->withUnreadCount(...) + * ->withChatID(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Participants|ParticipantsShape $participants + * @param Type|value-of $type + * @param Capabilities|CapabilitiesShape|null $capabilities + * @param Draft|DraftShape|null $draft + * @param Reminder|ReminderShape|null $reminder + * @param Snooze|SnoozeShape|null $snooze + * @param Status|value-of|null $status + */ + public static function with( + string $id, + string $accountID, + string $network, + Participants|array $participants, + string $title, + Type|string $type, + int $unreadCount, + string $chatID, + Capabilities|array|null $capabilities = null, + ?string $description = null, + Draft|array|null $draft = null, + ?string $imgURL = null, + ?bool $isArchived = null, + ?bool $isLowPriority = null, + ?bool $isMarkedUnread = null, + ?bool $isMuted = null, + ?bool $isPinned = null, + ?bool $isReadOnly = null, + ?\DateTimeInterface $lastActivity = null, + ?string $lastReadMessageSortKey = null, + ?string $localChatID = null, + ?int $messageExpirySeconds = null, + Reminder|array|null $reminder = null, + Snooze|array|null $snooze = null, + ?int $unreadMentionsCount = null, + Status|string|null $status = null, + ): self { + $self = new self; + + $self['id'] = $id; + $self['accountID'] = $accountID; + $self['network'] = $network; + $self['participants'] = $participants; + $self['title'] = $title; + $self['type'] = $type; + $self['unreadCount'] = $unreadCount; + $self['chatID'] = $chatID; + + null !== $capabilities && $self['capabilities'] = $capabilities; + null !== $description && $self['description'] = $description; + null !== $draft && $self['draft'] = $draft; + null !== $imgURL && $self['imgURL'] = $imgURL; + null !== $isArchived && $self['isArchived'] = $isArchived; + null !== $isLowPriority && $self['isLowPriority'] = $isLowPriority; + null !== $isMarkedUnread && $self['isMarkedUnread'] = $isMarkedUnread; + null !== $isMuted && $self['isMuted'] = $isMuted; + null !== $isPinned && $self['isPinned'] = $isPinned; + null !== $isReadOnly && $self['isReadOnly'] = $isReadOnly; + null !== $lastActivity && $self['lastActivity'] = $lastActivity; + null !== $lastReadMessageSortKey && $self['lastReadMessageSortKey'] = $lastReadMessageSortKey; + null !== $localChatID && $self['localChatID'] = $localChatID; + null !== $messageExpirySeconds && $self['messageExpirySeconds'] = $messageExpirySeconds; + null !== $reminder && $self['reminder'] = $reminder; + null !== $snooze && $self['snooze'] = $snooze; + null !== $unreadMentionsCount && $self['unreadMentionsCount'] = $unreadMentionsCount; + null !== $status && $self['status'] = $status; + + return $self; + } + + /** + * Unique identifier of the chat across Beeper. + */ + public function withID(string $id): self + { + $self = clone $this; + $self['id'] = $id; + + return $self; + } + + /** + * Account ID this chat belongs to. + */ + public function withAccountID(string $accountID): self + { + $self = clone $this; + $self['accountID'] = $accountID; + + return $self; + } + + /** + * Display-only human-readable account/network name. + */ + public function withNetwork(string $network): self + { + $self = clone $this; + $self['network'] = $network; + + return $self; + } + + /** + * Chat participants information. + * + * @param Participants|ParticipantsShape $participants + */ + public function withParticipants(Participants|array $participants): self + { + $self = clone $this; + $self['participants'] = $participants; + + return $self; + } + + /** + * Display title of the chat as computed by the client/server. + */ + public function withTitle(string $title): self + { + $self = clone $this; + $self['title'] = $title; + + return $self; + } + + /** + * Chat type: 'single' for direct messages, 'group' for group chats. + * + * @param Type|value-of $type + */ + public function withType(Type|string $type): self + { + $self = clone $this; + $self['type'] = $type; + + return $self; + } + + /** + * Number of unread messages. + */ + public function withUnreadCount(int $unreadCount): self + { + $self = clone $this; + $self['unreadCount'] = $unreadCount; + + return $self; + } + + /** + * Chat capabilities reported by the platform. + * + * @param Capabilities|CapabilitiesShape $capabilities + */ + public function withCapabilities(Capabilities|array $capabilities): self + { + $self = clone $this; + $self['capabilities'] = $capabilities; + + return $self; + } + + /** + * Group chat description/topic when available. + */ + public function withDescription(?string $description): self + { + $self = clone $this; + $self['description'] = $description; + + return $self; + } + + /** + * Current draft object for this chat, or null when no draft is set. + * + * @param Draft|DraftShape|null $draft + */ + public function withDraft(Draft|array|null $draft): self + { + $self = clone $this; + $self['draft'] = $draft; + + return $self; + } + + /** + * Local filesystem path to the chat avatar image when available. + */ + public function withImgURL(?string $imgURL): self + { + $self = clone $this; + $self['imgURL'] = $imgURL; + + return $self; + } + + /** + * True if chat is archived. + */ + public function withIsArchived(bool $isArchived): self + { + $self = clone $this; + $self['isArchived'] = $isArchived; + + return $self; + } + + /** + * True if chat is marked low priority. + */ + public function withIsLowPriority(bool $isLowPriority): self + { + $self = clone $this; + $self['isLowPriority'] = $isLowPriority; + + return $self; + } + + /** + * True if the chat was explicitly marked unread by the authenticated user. + */ + public function withIsMarkedUnread(bool $isMarkedUnread): self + { + $self = clone $this; + $self['isMarkedUnread'] = $isMarkedUnread; + + return $self; + } + + /** + * True if chat notifications are muted. + */ + public function withIsMuted(bool $isMuted): self + { + $self = clone $this; + $self['isMuted'] = $isMuted; + + return $self; + } + + /** + * True if chat is pinned. + */ + public function withIsPinned(bool $isPinned): self + { + $self = clone $this; + $self['isPinned'] = $isPinned; + + return $self; + } + + /** + * True if messages cannot be sent in this chat. + */ + public function withIsReadOnly(bool $isReadOnly): self + { + $self = clone $this; + $self['isReadOnly'] = $isReadOnly; + + return $self; + } + + /** + * Timestamp of last activity. + */ + public function withLastActivity(\DateTimeInterface $lastActivity): self + { + $self = clone $this; + $self['lastActivity'] = $lastActivity; + + return $self; + } + + /** + * Last read message sortKey. + */ + public function withLastReadMessageSortKey( + string $lastReadMessageSortKey + ): self { + $self = clone $this; + $self['lastReadMessageSortKey'] = $lastReadMessageSortKey; + + return $self; + } + + /** + * Local chat ID specific to this Beeper Desktop installation. + */ + public function withLocalChatID(?string $localChatID): self + { + $self = clone $this; + $self['localChatID'] = $localChatID; + + return $self; + } + + /** + * Disappearing-message timer in seconds when available. + */ + public function withMessageExpirySeconds(?int $messageExpirySeconds): self + { + $self = clone $this; + $self['messageExpirySeconds'] = $messageExpirySeconds; + + return $self; + } + + /** + * Current reminder for this chat, or null when no reminder is set. + * + * @param Reminder|ReminderShape|null $reminder + */ + public function withReminder(Reminder|array|null $reminder): self + { + $self = clone $this; + $self['reminder'] = $reminder; + + return $self; + } + + /** + * Current snooze state for this chat, or null when no snooze is set. + * + * @param Snooze|SnoozeShape|null $snooze + */ + public function withSnooze(Snooze|array|null $snooze): self + { + $self = clone $this; + $self['snooze'] = $snooze; + + return $self; + } + + /** + * Number of unread messages that mention the authenticated user or @room. + */ + public function withUnreadMentionsCount(int $unreadMentionsCount): self + { + $self = clone $this; + $self['unreadMentionsCount'] = $unreadMentionsCount; + + return $self; + } + + /** + * DEPRECATED - use id instead. Compatibility alias for older clients. + */ + public function withChatID(string $chatID): self + { + $self = clone $this; + $self['chatID'] = $chatID; + + return $self; + } + + /** + * DEPRECATED - legacy start-chat status for older clients. New clients should inspect the returned Chat instead. + * + * @param Status|value-of $status + */ + public function withStatus(Status|string $status): self + { + $self = clone $this; + $self['status'] = $status; + + return $self; + } +} diff --git a/src/Chats/ChatStartResponse/Status.php b/src/Chats/ChatStartResponse/Status.php new file mode 100644 index 0000000..4900cf2 --- /dev/null +++ b/src/Chats/ChatStartResponse/Status.php @@ -0,0 +1,17 @@ + */ + use SdkModel; + use SdkParams; + + /** + * Group chat description/topic. Support depends on the chat account and chat permissions. + */ + #[Optional(nullable: true)] + public ?string $description; + + /** + * Draft object to set or clear. Non-empty drafts are only accepted when the current draft is empty. Send draft=null to clear text and attachments together before setting a new draft. + */ + #[Optional(nullable: true)] + public ?Draft $draft; + + /** + * Local filesystem path to a group chat avatar image. Support depends on the chat account and chat permissions. + */ + #[Optional(nullable: true)] + public ?string $imgURL; + + /** + * Archive or unarchive the chat. + */ + #[Optional] + public ?bool $isArchived; + + /** + * Mark or unmark the chat as low priority when supported by the account. + */ + #[Optional] + public ?bool $isLowPriority; + + /** + * Mute or unmute the chat. + */ + #[Optional] + public ?bool $isMuted; + + /** + * Pin or unpin the chat when supported by the account. + */ + #[Optional] + public ?bool $isPinned; + + /** + * Disappearing-message timer in seconds, or null to clear when supported. + */ + #[Optional(nullable: true)] + public ?int $messageExpirySeconds; + + /** + * Custom chat title. Support depends on the chat account and chat permissions. + */ + #[Optional(nullable: true)] + public ?string $title; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Draft|DraftShape|null $draft + */ + public static function with( + ?string $description = null, + Draft|array|null $draft = null, + ?string $imgURL = null, + ?bool $isArchived = null, + ?bool $isLowPriority = null, + ?bool $isMuted = null, + ?bool $isPinned = null, + ?int $messageExpirySeconds = null, + ?string $title = null, + ): self { + $self = new self; + + null !== $description && $self['description'] = $description; + null !== $draft && $self['draft'] = $draft; + null !== $imgURL && $self['imgURL'] = $imgURL; + null !== $isArchived && $self['isArchived'] = $isArchived; + null !== $isLowPriority && $self['isLowPriority'] = $isLowPriority; + null !== $isMuted && $self['isMuted'] = $isMuted; + null !== $isPinned && $self['isPinned'] = $isPinned; + null !== $messageExpirySeconds && $self['messageExpirySeconds'] = $messageExpirySeconds; + null !== $title && $self['title'] = $title; + + return $self; + } + + /** + * Group chat description/topic. Support depends on the chat account and chat permissions. + */ + public function withDescription(?string $description): self + { + $self = clone $this; + $self['description'] = $description; + + return $self; + } + + /** + * Draft object to set or clear. Non-empty drafts are only accepted when the current draft is empty. Send draft=null to clear text and attachments together before setting a new draft. + * + * @param Draft|DraftShape|null $draft + */ + public function withDraft(Draft|array|null $draft): self + { + $self = clone $this; + $self['draft'] = $draft; + + return $self; + } + + /** + * Local filesystem path to a group chat avatar image. Support depends on the chat account and chat permissions. + */ + public function withImgURL(?string $imgURL): self + { + $self = clone $this; + $self['imgURL'] = $imgURL; + + return $self; + } + + /** + * Archive or unarchive the chat. + */ + public function withIsArchived(bool $isArchived): self + { + $self = clone $this; + $self['isArchived'] = $isArchived; + + return $self; + } + + /** + * Mark or unmark the chat as low priority when supported by the account. + */ + public function withIsLowPriority(bool $isLowPriority): self + { + $self = clone $this; + $self['isLowPriority'] = $isLowPriority; + + return $self; + } + + /** + * Mute or unmute the chat. + */ + public function withIsMuted(bool $isMuted): self + { + $self = clone $this; + $self['isMuted'] = $isMuted; + + return $self; + } + + /** + * Pin or unpin the chat when supported by the account. + */ + public function withIsPinned(bool $isPinned): self + { + $self = clone $this; + $self['isPinned'] = $isPinned; + + return $self; + } + + /** + * Disappearing-message timer in seconds, or null to clear when supported. + */ + public function withMessageExpirySeconds(?int $messageExpirySeconds): self + { + $self = clone $this; + $self['messageExpirySeconds'] = $messageExpirySeconds; + + return $self; + } + + /** + * Custom chat title. Support depends on the chat account and chat permissions. + */ + public function withTitle(?string $title): self + { + $self = clone $this; + $self['title'] = $title; + + return $self; + } +} diff --git a/src/Chats/ChatUpdateParams/Draft.php b/src/Chats/ChatUpdateParams/Draft.php new file mode 100644 index 0000000..51f926a --- /dev/null +++ b/src/Chats/ChatUpdateParams/Draft.php @@ -0,0 +1,101 @@ +|null + * } + */ +final class Draft implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Draft text. Plain text and Markdown are converted to Matrix HTML with the same rules used by send and edit. + */ + #[Required] + public string $text; + + /** + * Draft attachments keyed by attachment ID. Each attachment must reference an uploadID returned by the upload file endpoint. + * + * @var array|null $attachments + */ + #[Optional(map: Attachment::class)] + public ?array $attachments; + + /** + * `new Draft()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Draft::with(text: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Draft)->withText(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param array|null $attachments + */ + public static function with(string $text, ?array $attachments = null): self + { + $self = new self; + + $self['text'] = $text; + + null !== $attachments && $self['attachments'] = $attachments; + + return $self; + } + + /** + * Draft text. Plain text and Markdown are converted to Matrix HTML with the same rules used by send and edit. + */ + public function withText(string $text): self + { + $self = clone $this; + $self['text'] = $text; + + return $self; + } + + /** + * Draft attachments keyed by attachment ID. Each attachment must reference an uploadID returned by the upload file endpoint. + * + * @param array $attachments + */ + public function withAttachments(array $attachments): self + { + $self = clone $this; + $self['attachments'] = $attachments; + + return $self; + } +} diff --git a/src/Chats/ChatUpdateParams/Draft/Attachment.php b/src/Chats/ChatUpdateParams/Draft/Attachment.php new file mode 100644 index 0000000..2482277 --- /dev/null +++ b/src/Chats/ChatUpdateParams/Draft/Attachment.php @@ -0,0 +1,206 @@ +, + * } + */ +final class Attachment implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Upload ID from uploadAsset endpoint. Required to reference uploaded files. + */ + #[Required] + public string $uploadID; + + /** + * Optional draft attachment identifier. If omitted, a new identifier is generated. + */ + #[Optional] + public ?string $id; + + /** + * Duration in seconds (optional override of cached value). + */ + #[Optional] + public ?float $duration; + + /** + * Filename (optional override of cached value). + */ + #[Optional] + public ?string $fileName; + + /** + * MIME type (optional override of cached value). + */ + #[Optional] + public ?string $mimeType; + + /** + * Dimensions (optional override of cached value). + */ + #[Optional] + public ?Size $size; + + /** + * Attachment type hint (image, video, audio, file, gif, voice-note, sticker). If omitted, auto-detected from mimeType. + * + * @var value-of|null $type + */ + #[Optional(enum: Type::class)] + public ?string $type; + + /** + * `new Attachment()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Attachment::with(uploadID: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Attachment)->withUploadID(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Size|SizeShape|null $size + * @param Type|value-of|null $type + */ + public static function with( + string $uploadID, + ?string $id = null, + ?float $duration = null, + ?string $fileName = null, + ?string $mimeType = null, + Size|array|null $size = null, + Type|string|null $type = null, + ): self { + $self = new self; + + $self['uploadID'] = $uploadID; + + null !== $id && $self['id'] = $id; + null !== $duration && $self['duration'] = $duration; + null !== $fileName && $self['fileName'] = $fileName; + null !== $mimeType && $self['mimeType'] = $mimeType; + null !== $size && $self['size'] = $size; + null !== $type && $self['type'] = $type; + + return $self; + } + + /** + * Upload ID from uploadAsset endpoint. Required to reference uploaded files. + */ + public function withUploadID(string $uploadID): self + { + $self = clone $this; + $self['uploadID'] = $uploadID; + + return $self; + } + + /** + * Optional draft attachment identifier. If omitted, a new identifier is generated. + */ + public function withID(string $id): self + { + $self = clone $this; + $self['id'] = $id; + + return $self; + } + + /** + * Duration in seconds (optional override of cached value). + */ + public function withDuration(float $duration): self + { + $self = clone $this; + $self['duration'] = $duration; + + return $self; + } + + /** + * Filename (optional override of cached value). + */ + public function withFileName(string $fileName): self + { + $self = clone $this; + $self['fileName'] = $fileName; + + return $self; + } + + /** + * MIME type (optional override of cached value). + */ + public function withMimeType(string $mimeType): self + { + $self = clone $this; + $self['mimeType'] = $mimeType; + + return $self; + } + + /** + * Dimensions (optional override of cached value). + * + * @param Size|SizeShape $size + */ + public function withSize(Size|array $size): self + { + $self = clone $this; + $self['size'] = $size; + + return $self; + } + + /** + * Attachment type hint (image, video, audio, file, gif, voice-note, sticker). If omitted, auto-detected from mimeType. + * + * @param Type|value-of $type + */ + public function withType(Type|string $type): self + { + $self = clone $this; + $self['type'] = $type; + + return $self; + } +} diff --git a/src/Chats/ChatUpdateParams/Draft/Attachment/Size.php b/src/Chats/ChatUpdateParams/Draft/Attachment/Size.php new file mode 100644 index 0000000..269be18 --- /dev/null +++ b/src/Chats/ChatUpdateParams/Draft/Attachment/Size.php @@ -0,0 +1,76 @@ + */ + use SdkModel; + + #[Required] + public float $height; + + #[Required] + public float $width; + + /** + * `new Size()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Size::with(height: ..., width: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Size)->withHeight(...)->withWidth(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with(float $height, float $width): self + { + $self = new self; + + $self['height'] = $height; + $self['width'] = $width; + + return $self; + } + + public function withHeight(float $height): self + { + $self = clone $this; + $self['height'] = $height; + + return $self; + } + + public function withWidth(float $width): self + { + $self = clone $this; + $self['width'] = $width; + + return $self; + } +} diff --git a/src/Chats/ChatUpdateParams/Draft/Attachment/Type.php b/src/Chats/ChatUpdateParams/Draft/Attachment/Type.php new file mode 100644 index 0000000..a4ece19 --- /dev/null +++ b/src/Chats/ChatUpdateParams/Draft/Attachment/Type.php @@ -0,0 +1,25 @@ +withChatID(...)->withReactionKey(...) + * (new ReactionDeleteParams)->withChatID(...)->withMessageID(...) * ``` */ public function __construct() @@ -60,18 +60,18 @@ public function __construct() * * You must use named parameters to construct any parameters with a default value. */ - public static function with(string $chatID, string $reactionKey): self + public static function with(string $chatID, string $messageID): self { $self = new self; $self['chatID'] = $chatID; - $self['reactionKey'] = $reactionKey; + $self['messageID'] = $messageID; return $self; } /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ public function withChatID(string $chatID): self { @@ -82,12 +82,12 @@ public function withChatID(string $chatID): self } /** - * Reaction key to remove. + * Message ID. */ - public function withReactionKey(string $reactionKey): self + public function withMessageID(string $messageID): self { $self = clone $this; - $self['reactionKey'] = $reactionKey; + $self['messageID'] = $messageID; return $self; } diff --git a/src/Chats/Messages/Reactions/ReactionDeleteResponse.php b/src/Chats/Messages/Reactions/ReactionDeleteResponse.php index 3c431a5..4983aa2 100644 --- a/src/Chats/Messages/Reactions/ReactionDeleteResponse.php +++ b/src/Chats/Messages/Reactions/ReactionDeleteResponse.php @@ -19,13 +19,13 @@ final class ReactionDeleteResponse implements BaseModel use SdkModel; /** - * Whether the reaction was successfully removed. + * Always true. Indicates the reaction removal was queued; failures return an error response. */ #[Required] public bool $success = true; /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ #[Required] public string $chatID; @@ -84,7 +84,7 @@ public static function with( } /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ public function withChatID(string $chatID): self { @@ -117,7 +117,7 @@ public function withReactionKey(string $reactionKey): self } /** - * Whether the reaction was successfully removed. + * Always true. Indicates the reaction removal was queued; failures return an error response. */ public function withSuccess(bool $success): self { diff --git a/src/Chats/Reminders/ReminderCreateParams/Reminder.php b/src/Chats/Reminders/ReminderCreateParams/Reminder.php index 7d3510a..d6e3ff2 100644 --- a/src/Chats/Reminders/ReminderCreateParams/Reminder.php +++ b/src/Chats/Reminders/ReminderCreateParams/Reminder.php @@ -13,7 +13,7 @@ * Reminder configuration. * * @phpstan-type ReminderShape = array{ - * remindAtMs: float, dismissOnIncomingMessage?: bool|null + * remindAt: \DateTimeInterface, dismissOnIncomingMessage?: bool|null * } */ final class Reminder implements BaseModel @@ -22,10 +22,10 @@ final class Reminder implements BaseModel use SdkModel; /** - * Unix timestamp in milliseconds when reminder should trigger. + * Timestamp when the reminder should trigger. */ #[Required] - public float $remindAtMs; + public \DateTimeInterface $remindAt; /** * Cancel reminder if someone messages in the chat. @@ -38,13 +38,13 @@ final class Reminder implements BaseModel * * To enforce required parameters use * ``` - * Reminder::with(remindAtMs: ...) + * Reminder::with(remindAt: ...) * ``` * * Otherwise ensure the following setters are called * * ``` - * (new Reminder)->withRemindAtMs(...) + * (new Reminder)->withRemindAt(...) * ``` */ public function __construct() @@ -58,12 +58,12 @@ public function __construct() * You must use named parameters to construct any parameters with a default value. */ public static function with( - float $remindAtMs, + \DateTimeInterface $remindAt, ?bool $dismissOnIncomingMessage = null ): self { $self = new self; - $self['remindAtMs'] = $remindAtMs; + $self['remindAt'] = $remindAt; null !== $dismissOnIncomingMessage && $self['dismissOnIncomingMessage'] = $dismissOnIncomingMessage; @@ -71,12 +71,12 @@ public static function with( } /** - * Unix timestamp in milliseconds when reminder should trigger. + * Timestamp when the reminder should trigger. */ - public function withRemindAtMs(float $remindAtMs): self + public function withRemindAt(\DateTimeInterface $remindAt): self { $self = clone $this; - $self['remindAtMs'] = $remindAtMs; + $self['remindAt'] = $remindAt; return $self; } diff --git a/src/Client.php b/src/Client.php index b89e8ab..9806990 100644 --- a/src/Client.php +++ b/src/Client.php @@ -74,9 +74,7 @@ public function __construct( 'BEEPER_ACCESS_TOKEN' )); - $baseUrl ??= Util::getenv( - 'BEEPER_DESKTOP_BASE_URL' - ) ?: 'http://localhost:23373'; + $baseUrl ??= Util::getenv('BEEPER_BASE_URL') ?: 'http://localhost:23373'; $options = RequestOptions::parse( RequestOptions::with( @@ -88,18 +86,31 @@ public function __construct( $requestOptions, ); + /** @var array $headers */ + $headers = [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'User-Agent' => sprintf('beeperdesktop/PHP %s', VERSION), + 'X-Stainless-Lang' => 'php', + 'X-Stainless-Package-Version' => '0.0.1', + 'X-Stainless-Arch' => Util::machtype(), + 'X-Stainless-OS' => Util::ostype(), + 'X-Stainless-Runtime' => php_sapi_name(), + 'X-Stainless-Runtime-Version' => phpversion(), + ]; + + $customHeadersEnv = Util::getenv('BEEPER_CUSTOM_HEADERS'); + if (null !== $customHeadersEnv) { + foreach (explode("\n", $customHeadersEnv) as $line) { + $colon = strpos($line, ':'); + if (false !== $colon) { + $headers[trim(substr($line, 0, $colon))] = trim(substr($line, $colon + 1)); + } + } + } + parent::__construct( - headers: [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'User-Agent' => sprintf('beeperdesktop/PHP %s', VERSION), - 'X-Stainless-Lang' => 'php', - 'X-Stainless-Package-Version' => '0.0.1', - 'X-Stainless-Arch' => Util::machtype(), - 'X-Stainless-OS' => Util::ostype(), - 'X-Stainless-Runtime' => php_sapi_name(), - 'X-Stainless-Runtime-Version' => phpversion(), - ], + headers: $headers, baseUrl: $baseUrl, options: $options ); @@ -116,11 +127,11 @@ public function __construct( /** * @api * - * Focus Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill draft text and attachment. + * Focus Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill plain text and an image path. * * @param string $chatID Optional Beeper chat ID (or local chat ID) to focus after opening the app. If omitted, only opens/focuses the app. - * @param string $draftAttachmentPath optional draft attachment path to populate in the message input field - * @param string $draftText optional draft text to populate in the message input field + * @param string $draftAttachmentPath optional image path to populate in the message input field + * @param string $draftText optional plain text to populate in the message input field * @param string $messageID Optional message ID. Jumps to that message in the chat when opening. * @param RequestOpts|null $requestOptions * @@ -159,8 +170,18 @@ public function search( return $this->beeperDesktopClientService->search($query, $requestOptions); } + /** + * @param array{bearerAuth?: bool} $security + * + * @return array + */ + protected function authHeaders(array $security): array + { + return [...($security['bearerAuth'] ?? false) ? $this->bearerAuth() : []]; + } + /** @return array */ - protected function authHeaders(): array + protected function bearerAuth(): array { return $this->accessToken ? [ 'Authorization' => "Bearer {$this->accessToken}", @@ -174,6 +195,7 @@ protected function authHeaders(): array * @param array $query * @param array|null> $headers * @param RequestOpts|null $opts + * @param array{bearerAuth?: bool}|null $security * * @return array{NormalizedRequest, RequestOptions} */ @@ -184,14 +206,19 @@ protected function buildRequest( array $headers, mixed $body, RequestOptions|array|null $opts, + ?array $security = null, ): array { return parent::buildRequest( method: $method, path: $path, query: $query, - headers: [...$this->authHeaders(), ...$headers], + headers: [ + ...$this->authHeaders(security: ($security ?? ['bearerAuth' => true])), + ...$headers, + ], body: $body, opts: $opts, + security: $security, ); } } diff --git a/src/Core/Attributes/Required.php b/src/Core/Attributes/Required.php index bcb6957..d861eb6 100644 --- a/src/Core/Attributes/Required.php +++ b/src/Core/Attributes/Required.php @@ -25,9 +25,6 @@ class Required public readonly bool $nullable; - /** @var array */ - private static array $enumConverters = []; - /** * @param class-string|Converter|string|null $type * @param class-string<\BackedEnum>|Converter|null $enum @@ -52,7 +49,7 @@ public function __construct( $type ??= new MapOf($map); } if (null !== $enum) { - $type ??= $enum instanceof Converter ? $enum : self::enumConverter($enum); + $type ??= $enum instanceof Converter ? $enum : EnumOf::fromBackedEnum($enum); } $this->apiName = $apiName; @@ -60,16 +57,4 @@ public function __construct( $this->optional = false; $this->nullable = $nullable; } - - /** @property class-string<\BackedEnum> $enum */ - private static function enumConverter(string $enum): Converter - { - if (!isset(self::$enumConverters[$enum])) { - // @phpstan-ignore-next-line argument.type - $converter = new EnumOf(array_column($enum::cases(), column_key: 'value')); - self::$enumConverters[$enum] = $converter; - } - - return self::$enumConverters[$enum]; - } } diff --git a/src/Core/BaseClient.php b/src/Core/BaseClient.php index 14b35ea..bed6a0d 100644 --- a/src/Core/BaseClient.php +++ b/src/Core/BaseClient.php @@ -55,6 +55,7 @@ public function __construct( * @param string|int|list|null $unwrap * @param class-string>|null $page * @param class-string>|null $stream + * @param array{bearerAuth?: bool}|null $security * @param RequestOptions|array|null $options * * @return BaseResponse @@ -69,6 +70,7 @@ public function request( string|Converter|ConverterSource|null $convert = null, ?string $page = null, ?string $stream = null, + ?array $security = null, RequestOptions|array|null $options = [], ): BaseResponse { [$req, $opts] = $this->buildRequest( @@ -79,6 +81,7 @@ public function request( // @phpstan-ignore argument.type headers: $headers, body: $body, + security: $security, // @phpstan-ignore argument.type opts: $options, ); @@ -113,6 +116,7 @@ protected function generateIdempotencyKey(): string * @param array $query * @param array|null> $headers * @param RequestOpts|null $opts + * @param array{bearerAuth?: bool}|null $security * * @return array{NormalizedRequest, RequestOptions} */ @@ -123,6 +127,7 @@ protected function buildRequest( array $headers, mixed $body, RequestOptions|array|null $opts, + ?array $security = null, ): array { $options = RequestOptions::parse($this->options, $opts); diff --git a/src/Core/Conversion.php b/src/Core/Conversion.php index 494ec69..84a6f56 100644 --- a/src/Core/Conversion.php +++ b/src/Core/Conversion.php @@ -8,6 +8,7 @@ use BeeperDesktop\Core\Conversion\Contracts\Converter; use BeeperDesktop\Core\Conversion\Contracts\ConverterSource; use BeeperDesktop\Core\Conversion\DumpState; +use BeeperDesktop\Core\Conversion\EnumOf; /** * @internal @@ -21,6 +22,10 @@ public static function dump_unknown(mixed $value, DumpState $state): mixed } if (is_object($value)) { + if ($value instanceof FileParam) { + return $value; + } + if (is_a($value, class: ConverterSource::class)) { return $value::converter()->dump($value, state: $state); } @@ -61,6 +66,13 @@ public static function coerce(Converter|ConverterSource|string $target, mixed $v return $target->coerce($value, state: $state); } + // BackedEnum class-name targets: wrap in EnumOf so enum values are scored + // against the enum's cases. Without this, tryConvert's default case scores + // any class-name target as `no`, even when the value is a valid enum member. + if (is_a($target, class: \BackedEnum::class, allow_string: true)) { + return EnumOf::fromBackedEnum($target)->coerce($value, state: $state); + } + return self::tryConvert($target, value: $value, state: $state); } @@ -74,6 +86,13 @@ public static function dump(Converter|ConverterSource|string $target, mixed $val return $target::converter()->dump($value, state: $state); } + // BackedEnum class-name targets: wrap in EnumOf so enum values are scored + // against the enum's cases. Without this, tryConvert's default case scores + // any class-name target as `no`, even when the value is a valid enum member. + if (is_a($target, class: \BackedEnum::class, allow_string: true)) { + return EnumOf::fromBackedEnum($target)->dump($value, state: $state); + } + self::tryConvert($target, value: $value, state: $state); return self::dump_unknown($value, state: $state); @@ -170,6 +189,37 @@ private static function tryConvert(Converter|ConverterSource|string $target, mix return $value; + case 'DateTimeInterface': + case 'DateTimeImmutable': + if (is_string($value)) { + try { + ++$state->maybe; + + return new \DateTimeImmutable($value); + } catch (\Exception) { + --$state->maybe; + } + } + + ++$state->no; + + return $value; + + case 'DateTime': + if (is_string($value)) { + try { + ++$state->maybe; + + return new \DateTime($value); + } catch (\Exception) { + --$state->maybe; + } + } + + ++$state->no; + + return $value; + default: ++$state->no; diff --git a/src/Core/Conversion/EnumOf.php b/src/Core/Conversion/EnumOf.php index a4d791b..b44c92c 100644 --- a/src/Core/Conversion/EnumOf.php +++ b/src/Core/Conversion/EnumOf.php @@ -14,6 +14,9 @@ final class EnumOf implements Converter { private readonly string $type; + /** @var array, self> */ + private static array $cache = []; + /** * @param list $members */ @@ -26,6 +29,13 @@ public function __construct(private readonly array $members) $this->type = $type; } + /** @param class-string<\BackedEnum> $enum */ + public static function fromBackedEnum(string $enum): self + { + // @phpstan-ignore-next-line argument.type + return self::$cache[$enum] ??= new self(array_column($enum::cases(), column_key: 'value')); + } + public function coerce(mixed $value, CoerceState $state): mixed { $this->tally($value, state: $state); @@ -42,9 +52,10 @@ public function dump(mixed $value, DumpState $state): mixed private function tally(mixed $value, CoerceState|DumpState $state): void { - if (in_array($value, haystack: $this->members, strict: true)) { + $needle = $value instanceof \BackedEnum ? $value->value : $value; + if (in_array($needle, haystack: $this->members, strict: true)) { ++$state->yes; - } elseif ($this->type === gettype($value)) { + } elseif ($this->type === gettype($needle)) { ++$state->maybe; } else { ++$state->no; diff --git a/src/Core/FileParam.php b/src/Core/FileParam.php new file mode 100644 index 0000000..eb8db4a --- /dev/null +++ b/src/Core/FileParam.php @@ -0,0 +1,63 @@ +files->upload(file: FileParam::fromResource(fopen('data.csv', 'r'))); + * + * // From a string: + * $client->files->upload(file: FileParam::fromString('csv data...', 'data.csv')); + * ``` + */ +final class FileParam +{ + public const DEFAULT_CONTENT_TYPE = 'application/octet-stream'; + + /** + * @param resource|string $data the file content as a resource or string + */ + private function __construct( + public readonly mixed $data, + public readonly string $filename, + public readonly string $contentType = self::DEFAULT_CONTENT_TYPE, + ) {} + + /** + * Create a FileParam from an open resource (e.g. from fopen()). + * + * @param resource $resource an open file resource + * @param string|null $filename Override the filename. Defaults to the resource URI basename. + * @param string $contentType override the content type + */ + public static function fromResource(mixed $resource, ?string $filename = null, string $contentType = self::DEFAULT_CONTENT_TYPE): self + { + if (!is_resource($resource)) { + throw new \InvalidArgumentException('Expected a resource, got '.get_debug_type($resource)); + } + + if (null === $filename) { + $meta = stream_get_meta_data($resource); + $filename = basename($meta['uri'] ?? 'upload'); + } + + return new self($resource, filename: $filename, contentType: $contentType); + } + + /** + * Create a FileParam from a string. + * + * @param string $content the file content + * @param string $filename the filename for the Content-Disposition header + * @param string $contentType override the content type + */ + public static function fromString(string $content, string $filename, string $contentType = self::DEFAULT_CONTENT_TYPE): self + { + return new self($content, filename: $filename, contentType: $contentType); + } +} diff --git a/src/Core/Util.php b/src/Core/Util.php index 35a9a0b..b385aff 100644 --- a/src/Core/Util.php +++ b/src/Core/Util.php @@ -283,7 +283,7 @@ public static function withSetBody( if (preg_match('/^multipart\/form-data/', $contentType)) { [$boundary, $gen] = self::encodeMultipartStreaming($body); - $encoded = implode('', iterator_to_array($gen)); + $encoded = implode('', iterator_to_array($gen, preserve_keys: false)); $stream = $factory->createStream($encoded); /** @var RequestInterface */ @@ -447,11 +447,18 @@ private static function writeMultipartContent( ): \Generator { $contentLine = "Content-Type: %s\r\n\r\n"; - if (is_resource($val)) { - yield sprintf($contentLine, $contentType ?? 'application/octet-stream'); - while (!feof($val)) { - if ($read = fread($val, length: self::BUF_SIZE)) { - yield $read; + if ($val instanceof FileParam) { + $ct = $val->contentType ?? $contentType; + + yield sprintf($contentLine, $ct); + $data = $val->data; + if (is_string($data)) { + yield $data; + } else { // resource + while (!feof($data)) { + if ($read = fread($data, length: self::BUF_SIZE)) { + yield $read; + } } } } elseif (is_string($val) || is_numeric($val) || is_bool($val)) { @@ -483,17 +490,48 @@ private static function writeMultipartChunk( yield 'Content-Disposition: form-data'; if (!is_null($key)) { - $name = rawurlencode(self::strVal($key)); + $name = str_replace(['"', "\r", "\n"], replace: '', subject: $key); yield "; name=\"{$name}\""; } + // File uploads require a filename in the Content-Disposition header, + // e.g. `Content-Disposition: form-data; name="file"; filename="data.csv"` + // Without this, many servers will reject the upload with a 400. + if ($val instanceof FileParam) { + $filename = str_replace(['"', "\r", "\n"], replace: '', subject: $val->filename); + + yield "; filename=\"{$filename}\""; + } + yield "\r\n"; foreach (self::writeMultipartContent($val, closing: $closing) as $chunk) { yield $chunk; } } + /** + * Expands list arrays into separate multipart parts, applying the configured array key format. + * + * @param list $closing + * + * @return \Generator + */ + private static function writeMultipartField( + string $boundary, + ?string $key, + mixed $val, + array &$closing + ): \Generator { + if (is_array($val) && array_is_list($val)) { + foreach ($val as $item) { + yield from self::writeMultipartField(boundary: $boundary, key: $key, val: $item, closing: $closing); + } + } else { + yield from self::writeMultipartChunk(boundary: $boundary, key: $key, val: $val, closing: $closing); + } + } + /** * @param bool|int|float|string|resource|\Traversable|array|null $body * @@ -508,14 +546,10 @@ private static function encodeMultipartStreaming(mixed $body): array try { if (is_array($body) || is_object($body)) { foreach ((array) $body as $key => $val) { - foreach (static::writeMultipartChunk(boundary: $boundary, key: $key, val: $val, closing: $closing) as $chunk) { - yield $chunk; - } + yield from static::writeMultipartField(boundary: $boundary, key: $key, val: $val, closing: $closing); } } else { - foreach (static::writeMultipartChunk(boundary: $boundary, key: null, val: $body, closing: $closing) as $chunk) { - yield $chunk; - } + yield from static::writeMultipartField(boundary: $boundary, key: null, val: $body, closing: $closing); } yield "--{$boundary}--\r\n"; diff --git a/src/CursorNoLimit.php b/src/CursorNoLimit.php index 5ea1b0c..9398fb2 100644 --- a/src/CursorNoLimit.php +++ b/src/CursorNoLimit.php @@ -2,132 +2,122 @@ namespace BeeperDesktop; -use BeeperDesktop\Core\Conversion; use BeeperDesktop\Core\Attributes\Optional; use BeeperDesktop\Core\Concerns\SdkModel; use BeeperDesktop\Core\Concerns\SdkPage; use BeeperDesktop\Core\Contracts\BaseModel; use BeeperDesktop\Core\Contracts\BasePage; -use BeeperDesktop\Core\Conversion\ListOf; +use BeeperDesktop\Core\Conversion; use BeeperDesktop\Core\Conversion\Contracts\Converter; use BeeperDesktop\Core\Conversion\Contracts\ConverterSource; +use BeeperDesktop\Core\Conversion\ListOf; use Psr\Http\Message\ResponseInterface; /** - * - * @phpstan-type CursorNoLimitShape = array{ - * items?: list>|null, - * hasMore?: bool|null, - * oldestCursor?: string|null, - * newestCursor?: string|null, - * } - * @template TItem - * @implements BasePage - * + * @phpstan-type CursorNoLimitShape = array{ + * items?: list>|null, + * hasMore?: bool|null, + * oldestCursor?: string|null, + * newestCursor?: string|null, + * } + * + * @template TItem + * + * @implements BasePage */ final class CursorNoLimit implements BaseModel, BasePage { - /** @use SdkModel */ - use SdkModel; - - /** @use SdkPage */ - use SdkPage; - - /** @var list|null $items */ - #[Optional(list: 'mixed')] - public ?array $items; - - /** @var bool|null $hasMore */ - #[Optional] - public ?bool $hasMore; - - /** @var string|null $oldestCursor */ - #[Optional(nullable: true)] - public ?string $oldestCursor; - - /** @var string|null $newestCursor */ - #[Optional(nullable: true)] - public ?string $newestCursor; - - /** @return list */ - function getItems(): array { - // @phpstan-ignore-next-line return.type - return $this->offsetGet('items') ?? []; - } - - /** - * @internal - * - * @return array{ - * array{ - * method: string, - * path: string, - * query: array, - * headers: array>, - * body: mixed, - * }, - * RequestOptions, - * }|null - */ - function nextRequest(): ?array { - if (!($this->hasMore ?? null)||!count($this->getItems())) { - return null; - + /** @use SdkModel */ + use SdkModel; + + /** @use SdkPage */ + use SdkPage; + + /** @var list|null $items */ + #[Optional(list: 'mixed')] + public ?array $items; + + #[Optional] + public ?bool $hasMore; + + #[Optional(nullable: true)] + public ?string $oldestCursor; + + #[Optional(nullable: true)] + public ?string $newestCursor; + + /** + * @internal + * + * @param array{ + * method: string, + * path: string, + * query: array, + * headers: array|null>, + * body: mixed, + * } $requestInfo + */ + public function __construct( + private string|Converter|ConverterSource $convert, + private Client $client, + private array $requestInfo, + private RequestOptions $options, + private ResponseInterface $response, + private mixed $parsedBody, + ) { + $this->initialize(); + + if (!is_array($this->parsedBody)) { + return; + } + + // @phpstan-ignore-next-line argument.type + self::__unserialize($this->parsedBody); + + if (is_array($items = $this->offsetGet('items'))) { + $parsed = Conversion::coerce(new ListOf($convert), value: $items); + // @phpstan-ignore-next-line + $this->offsetSet('items', value: $parsed); + } } - if (!($prev = $this->newestCursor ?? null)&&!($next = $this - ->oldestCursor ?? null)) { - return null; - + /** @return list */ + public function getItems(): array + { + // @phpstan-ignore-next-line return.type + return $this->offsetGet('items') ?? []; } - $nextRequest = array_merge_recursive( - $this->requestInfo, - ['query' => empty($prev) ? ['cursor' => $next] : [=> $prev]], - ); - - // @phpstan-ignore-next-line return.type - return [$nextRequest, $this->options]; - } - - /** - * @internal - * - * @param string|Converter|ConverterSource $convert - * @param Client $client - * @param array{ - * method: string, - * path: string, - * query: array, - * headers: array>, - * body: mixed, - * } $requestInfo - * @param RequestOptions $options - * @param mixed $parsedBody - */ - function __construct( - private string|Converter|ConverterSource $convert, - private Client $client, - private array $requestInfo, - private RequestOptions $options, - private ResponseInterface $response, - private mixed $parsedBody, - ) { - $this->initialize(); - - if (!is_array($this->parsedBody)) { - return; - - } - - // @phpstan-ignore-next-line argument.type - self::__unserialize($this->parsedBody); - - if (is_array($items = $this->offsetGet('items'))) { - $parsed = Conversion::coerce(new ListOf($convert), value: $items); - // @phpstan-ignore-next-line - $this->offsetSet('items', value: $parsed); - + /** + * @internal + * + * @return array{ + * array{ + * method: string, + * path: string, + * query: array, + * headers: array|null>, + * body: mixed, + * }, + * RequestOptions, + * }|null + */ + public function nextRequest(): ?array + { + if (!($this->hasMore ?? null) || !count($this->getItems())) { + return null; + } + + if (!($next = $this->oldestCursor ?? null)) { + return null; + } + + $nextRequest = array_merge_recursive( + $this->requestInfo, + ['query' => ['cursor' => $next]] + ); + + // @phpstan-ignore-next-line return.type + return [$nextRequest, $this->options]; } - } -} \ No newline at end of file +} diff --git a/src/CursorSearch.php b/src/CursorSearch.php index 9d46758..248cae5 100644 --- a/src/CursorSearch.php +++ b/src/CursorSearch.php @@ -2,132 +2,122 @@ namespace BeeperDesktop; -use BeeperDesktop\Core\Conversion; use BeeperDesktop\Core\Attributes\Optional; use BeeperDesktop\Core\Concerns\SdkModel; use BeeperDesktop\Core\Concerns\SdkPage; use BeeperDesktop\Core\Contracts\BaseModel; use BeeperDesktop\Core\Contracts\BasePage; -use BeeperDesktop\Core\Conversion\ListOf; +use BeeperDesktop\Core\Conversion; use BeeperDesktop\Core\Conversion\Contracts\Converter; use BeeperDesktop\Core\Conversion\Contracts\ConverterSource; +use BeeperDesktop\Core\Conversion\ListOf; use Psr\Http\Message\ResponseInterface; /** - * - * @phpstan-type CursorSearchShape = array{ - * items?: list>|null, - * hasMore?: bool|null, - * oldestCursor?: string|null, - * newestCursor?: string|null, - * } - * @template TItem - * @implements BasePage - * + * @phpstan-type CursorSearchShape = array{ + * items?: list>|null, + * hasMore?: bool|null, + * oldestCursor?: string|null, + * newestCursor?: string|null, + * } + * + * @template TItem + * + * @implements BasePage */ final class CursorSearch implements BaseModel, BasePage { - /** @use SdkModel */ - use SdkModel; - - /** @use SdkPage */ - use SdkPage; - - /** @var list|null $items */ - #[Optional(list: 'mixed')] - public ?array $items; - - /** @var bool|null $hasMore */ - #[Optional] - public ?bool $hasMore; - - /** @var string|null $oldestCursor */ - #[Optional(nullable: true)] - public ?string $oldestCursor; - - /** @var string|null $newestCursor */ - #[Optional(nullable: true)] - public ?string $newestCursor; - - /** @return list */ - function getItems(): array { - // @phpstan-ignore-next-line return.type - return $this->offsetGet('items') ?? []; - } - - /** - * @internal - * - * @return array{ - * array{ - * method: string, - * path: string, - * query: array, - * headers: array>, - * body: mixed, - * }, - * RequestOptions, - * }|null - */ - function nextRequest(): ?array { - if (!($this->hasMore ?? null)||!count($this->getItems())) { - return null; - + /** @use SdkModel */ + use SdkModel; + + /** @use SdkPage */ + use SdkPage; + + /** @var list|null $items */ + #[Optional(list: 'mixed')] + public ?array $items; + + #[Optional] + public ?bool $hasMore; + + #[Optional(nullable: true)] + public ?string $oldestCursor; + + #[Optional(nullable: true)] + public ?string $newestCursor; + + /** + * @internal + * + * @param array{ + * method: string, + * path: string, + * query: array, + * headers: array|null>, + * body: mixed, + * } $requestInfo + */ + public function __construct( + private string|Converter|ConverterSource $convert, + private Client $client, + private array $requestInfo, + private RequestOptions $options, + private ResponseInterface $response, + private mixed $parsedBody, + ) { + $this->initialize(); + + if (!is_array($this->parsedBody)) { + return; + } + + // @phpstan-ignore-next-line argument.type + self::__unserialize($this->parsedBody); + + if (is_array($items = $this->offsetGet('items'))) { + $parsed = Conversion::coerce(new ListOf($convert), value: $items); + // @phpstan-ignore-next-line + $this->offsetSet('items', value: $parsed); + } } - if (!($prev = $this->newestCursor ?? null)&&!($next = $this - ->oldestCursor ?? null)) { - return null; - + /** @return list */ + public function getItems(): array + { + // @phpstan-ignore-next-line return.type + return $this->offsetGet('items') ?? []; } - $nextRequest = array_merge_recursive( - $this->requestInfo, - ['query' => empty($prev) ? ['cursor' => $next] : [=> $prev]], - ); - - // @phpstan-ignore-next-line return.type - return [$nextRequest, $this->options]; - } - - /** - * @internal - * - * @param string|Converter|ConverterSource $convert - * @param Client $client - * @param array{ - * method: string, - * path: string, - * query: array, - * headers: array>, - * body: mixed, - * } $requestInfo - * @param RequestOptions $options - * @param mixed $parsedBody - */ - function __construct( - private string|Converter|ConverterSource $convert, - private Client $client, - private array $requestInfo, - private RequestOptions $options, - private ResponseInterface $response, - private mixed $parsedBody, - ) { - $this->initialize(); - - if (!is_array($this->parsedBody)) { - return; - - } - - // @phpstan-ignore-next-line argument.type - self::__unserialize($this->parsedBody); - - if (is_array($items = $this->offsetGet('items'))) { - $parsed = Conversion::coerce(new ListOf($convert), value: $items); - // @phpstan-ignore-next-line - $this->offsetSet('items', value: $parsed); - + /** + * @internal + * + * @return array{ + * array{ + * method: string, + * path: string, + * query: array, + * headers: array|null>, + * body: mixed, + * }, + * RequestOptions, + * }|null + */ + public function nextRequest(): ?array + { + if (!($this->hasMore ?? null) || !count($this->getItems())) { + return null; + } + + if (!($next = $this->oldestCursor ?? null)) { + return null; + } + + $nextRequest = array_merge_recursive( + $this->requestInfo, + ['query' => ['cursor' => $next]] + ); + + // @phpstan-ignore-next-line return.type + return [$nextRequest, $this->options]; } - } -} \ No newline at end of file +} diff --git a/src/CursorSortKey.php b/src/CursorSortKey.php deleted file mode 100644 index 7cdfa0c..0000000 --- a/src/CursorSortKey.php +++ /dev/null @@ -1,113 +0,0 @@ -|null, hasMore?: bool|null - * } - * - * @template TItem - * - * @implements BasePage - */ -final class CursorSortKey implements BaseModel, BasePage -{ - /** @use SdkModel */ - use SdkModel; - - /** @use SdkPage */ - use SdkPage; - - /** @var list|null $items */ - #[Optional(list: 'mixed')] - public ?array $items; - - #[Optional] - public ?bool $hasMore; - - /** - * @internal - * - * @param array{ - * method: string, - * path: string, - * query: array, - * headers: array|null>, - * body: mixed, - * } $requestInfo - */ - public function __construct( - private string|Converter|ConverterSource $convert, - private Client $client, - private array $requestInfo, - private RequestOptions $options, - private ResponseInterface $response, - private mixed $parsedBody, - ) { - $this->initialize(); - - if (!is_array($this->parsedBody)) { - return; - } - - // @phpstan-ignore-next-line argument.type - self::__unserialize($this->parsedBody); - - if (is_array($items = $this->offsetGet('items'))) { - $parsed = Conversion::coerce(new ListOf($convert), value: $items); - // @phpstan-ignore-next-line - $this->offsetSet('items', value: $parsed); - } - } - - /** @return list */ - public function getItems(): array - { - // @phpstan-ignore-next-line return.type - return $this->offsetGet('items') ?? []; - } - - /** - * @internal - * - * @return array{ - * array{ - * method: string, - * path: string, - * query: array, - * headers: array|null>, - * body: mixed, - * }, - * RequestOptions, - * }|null - */ - public function nextRequest(): ?array - { - $items = $this->getItems(); - if (!($this - ->hasMore ?? null) || !count($items) || !($key = array_key_last($items))) { - return null; - } - - $nextRequest = array_merge_recursive( - $this->requestInfo, - ['query' => ['sortKey' => $items[$key]]] - ); - - // @phpstan-ignore-next-line return.type - return [$nextRequest, $this->options]; - } -} diff --git a/src/CursorSortKey/Item.php b/src/CursorSortKey/Item.php deleted file mode 100644 index f3fe595..0000000 --- a/src/CursorSortKey/Item.php +++ /dev/null @@ -1,48 +0,0 @@ - */ - use SdkModel; - - #[Optional] - public ?string $sortKey; - - public function __construct() - { - $this->initialize(); - } - - /** - * Construct an instance from the required parameters. - * - * You must use named parameters to construct any parameters with a default value. - */ - public static function with(?string $sortKey = null): self - { - $self = new self; - - null !== $sortKey && $self['sortKey'] = $sortKey; - - return $self; - } - - public function withSortKey(string $sortKey): self - { - $self = clone $this; - $self['sortKey'] = $sortKey; - - return $self; - } -} diff --git a/src/Info/InfoGetResponse/Server.php b/src/Info/InfoGetResponse/Server.php index 3fcf9b4..bcc6df9 100644 --- a/src/Info/InfoGetResponse/Server.php +++ b/src/Info/InfoGetResponse/Server.php @@ -24,7 +24,7 @@ final class Server implements BaseModel use SdkModel; /** - * Base URL of the Connect server. + * Base URL of the Beeper Desktop API server. */ #[Required('base_url')] public string $baseURL; @@ -117,7 +117,7 @@ public static function with( } /** - * Base URL of the Connect server. + * Base URL of the Beeper Desktop API server. */ public function withBaseURL(string $baseURL): self { diff --git a/src/Message.php b/src/Message.php index f1a8846..22187db 100644 --- a/src/Message.php +++ b/src/Message.php @@ -8,11 +8,18 @@ use BeeperDesktop\Core\Attributes\Required; use BeeperDesktop\Core\Concerns\SdkModel; use BeeperDesktop\Core\Contracts\BaseModel; +use BeeperDesktop\Message\Link; +use BeeperDesktop\Message\Seen; +use BeeperDesktop\Message\SendStatus; use BeeperDesktop\Message\Type; /** + * @phpstan-import-type SeenVariants from \BeeperDesktop\Message\Seen * @phpstan-import-type AttachmentShape from \BeeperDesktop\Attachment + * @phpstan-import-type LinkShape from \BeeperDesktop\Message\Link * @phpstan-import-type ReactionShape from \BeeperDesktop\Reaction + * @phpstan-import-type SeenShape from \BeeperDesktop\Message\Seen + * @phpstan-import-type SendStatusShape from \BeeperDesktop\Message\SendStatus * * @phpstan-type MessageShape = array{ * id: string, @@ -22,11 +29,18 @@ * sortKey: string, * timestamp: \DateTimeInterface, * attachments?: list|null, + * editedTimestamp?: \DateTimeInterface|null, + * isDeleted?: bool|null, + * isHidden?: bool|null, * isSender?: bool|null, * isUnread?: bool|null, * linkedMessageID?: string|null, + * links?: list|null, + * mentions?: list|null, * reactions?: list|null, + * seen?: SeenShape|null, * senderName?: string|null, + * sendStatus?: null|SendStatus|SendStatusShape, * text?: string|null, * type?: null|Type|value-of, * } @@ -49,13 +63,13 @@ final class Message implements BaseModel public string $accountID; /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ #[Required] public string $chatID; /** - * Sender user ID. + * Matrix-style fully-qualified sender user ID, usually including a bridge prefix and homeserver. */ #[Required] public string $senderID; @@ -80,6 +94,24 @@ final class Message implements BaseModel #[Optional(list: Attachment::class)] public ?array $attachments; + /** + * Timestamp when the message was edited, if known. + */ + #[Optional] + public ?\DateTimeInterface $editedTimestamp; + + /** + * True if the message has been deleted. + */ + #[Optional] + public ?bool $isDeleted; + + /** + * True if the message is hidden from normal display. + */ + #[Optional] + public ?bool $isHidden; + /** * True if the authenticated user sent the message. */ @@ -98,6 +130,22 @@ final class Message implements BaseModel #[Optional] public ?string $linkedMessageID; + /** + * Link previews included with this message, if any. + * + * @var list|null $links + */ + #[Optional(list: Link::class)] + public ?array $links; + + /** + * Mentioned user IDs, @room, or null for legacy messages that require text scanning. + * + * @var list|null $mentions + */ + #[Optional(list: 'string', nullable: true)] + public ?array $mentions; + /** * Reactions to the message, if any. * @@ -106,6 +154,14 @@ final class Message implements BaseModel #[Optional(list: Reaction::class)] public ?array $reactions; + /** + * Read receipt state for this message, when available. + * + * @var SeenVariants|null $seen + */ + #[Optional(union: Seen::class)] + public bool|\DateTimeInterface|array|null $seen; + /** * Resolved sender display name (impersonator/full name/username/participant name). */ @@ -113,7 +169,13 @@ final class Message implements BaseModel public ?string $senderName; /** - * Plain-text body if present. May include a JSON fallback with text entities for rich messages. + * Message send status for this message, when reported by the bridge. + */ + #[Optional] + public ?SendStatus $sendStatus; + + /** + * Matrix HTML body if present. */ #[Optional] public ?string $text; @@ -164,7 +226,11 @@ public function __construct() * You must use named parameters to construct any parameters with a default value. * * @param list|null $attachments + * @param list|null $links + * @param list|null $mentions * @param list|null $reactions + * @param SeenShape|null $seen + * @param SendStatus|SendStatusShape|null $sendStatus * @param Type|value-of|null $type */ public static function with( @@ -175,11 +241,18 @@ public static function with( string $sortKey, \DateTimeInterface $timestamp, ?array $attachments = null, + ?\DateTimeInterface $editedTimestamp = null, + ?bool $isDeleted = null, + ?bool $isHidden = null, ?bool $isSender = null, ?bool $isUnread = null, ?string $linkedMessageID = null, + ?array $links = null, + ?array $mentions = null, ?array $reactions = null, + bool|\DateTimeInterface|array|null $seen = null, ?string $senderName = null, + SendStatus|array|null $sendStatus = null, ?string $text = null, Type|string|null $type = null, ): self { @@ -193,11 +266,18 @@ public static function with( $self['timestamp'] = $timestamp; null !== $attachments && $self['attachments'] = $attachments; + null !== $editedTimestamp && $self['editedTimestamp'] = $editedTimestamp; + null !== $isDeleted && $self['isDeleted'] = $isDeleted; + null !== $isHidden && $self['isHidden'] = $isHidden; null !== $isSender && $self['isSender'] = $isSender; null !== $isUnread && $self['isUnread'] = $isUnread; null !== $linkedMessageID && $self['linkedMessageID'] = $linkedMessageID; + null !== $links && $self['links'] = $links; + null !== $mentions && $self['mentions'] = $mentions; null !== $reactions && $self['reactions'] = $reactions; + null !== $seen && $self['seen'] = $seen; null !== $senderName && $self['senderName'] = $senderName; + null !== $sendStatus && $self['sendStatus'] = $sendStatus; null !== $text && $self['text'] = $text; null !== $type && $self['type'] = $type; @@ -227,7 +307,7 @@ public function withAccountID(string $accountID): self } /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ public function withChatID(string $chatID): self { @@ -238,7 +318,7 @@ public function withChatID(string $chatID): self } /** - * Sender user ID. + * Matrix-style fully-qualified sender user ID, usually including a bridge prefix and homeserver. */ public function withSenderID(string $senderID): self { @@ -283,6 +363,40 @@ public function withAttachments(array $attachments): self return $self; } + /** + * Timestamp when the message was edited, if known. + */ + public function withEditedTimestamp( + \DateTimeInterface $editedTimestamp + ): self { + $self = clone $this; + $self['editedTimestamp'] = $editedTimestamp; + + return $self; + } + + /** + * True if the message has been deleted. + */ + public function withIsDeleted(bool $isDeleted): self + { + $self = clone $this; + $self['isDeleted'] = $isDeleted; + + return $self; + } + + /** + * True if the message is hidden from normal display. + */ + public function withIsHidden(bool $isHidden): self + { + $self = clone $this; + $self['isHidden'] = $isHidden; + + return $self; + } + /** * True if the authenticated user sent the message. */ @@ -316,6 +430,32 @@ public function withLinkedMessageID(string $linkedMessageID): self return $self; } + /** + * Link previews included with this message, if any. + * + * @param list $links + */ + public function withLinks(array $links): self + { + $self = clone $this; + $self['links'] = $links; + + return $self; + } + + /** + * Mentioned user IDs, @room, or null for legacy messages that require text scanning. + * + * @param list|null $mentions + */ + public function withMentions(?array $mentions): self + { + $self = clone $this; + $self['mentions'] = $mentions; + + return $self; + } + /** * Reactions to the message, if any. * @@ -329,6 +469,19 @@ public function withReactions(array $reactions): self return $self; } + /** + * Read receipt state for this message, when available. + * + * @param SeenShape $seen + */ + public function withSeen(bool|\DateTimeInterface|array $seen): self + { + $self = clone $this; + $self['seen'] = $seen; + + return $self; + } + /** * Resolved sender display name (impersonator/full name/username/participant name). */ @@ -341,7 +494,20 @@ public function withSenderName(string $senderName): self } /** - * Plain-text body if present. May include a JSON fallback with text entities for rich messages. + * Message send status for this message, when reported by the bridge. + * + * @param SendStatus|SendStatusShape $sendStatus + */ + public function withSendStatus(SendStatus|array $sendStatus): self + { + $self = clone $this; + $self['sendStatus'] = $sendStatus; + + return $self; + } + + /** + * Matrix HTML body if present. */ public function withText(string $text): self { diff --git a/src/Message/Link.php b/src/Message/Link.php new file mode 100644 index 0000000..c41dfc5 --- /dev/null +++ b/src/Message/Link.php @@ -0,0 +1,202 @@ + */ + use SdkModel; + + /** + * Link preview title. + */ + #[Required] + public string $title; + + /** + * Resolved link URL. + */ + #[Required] + public string $url; + + /** + * Favicon URL if available. May be temporary or local-only to this device; download promptly if durable access is needed. + */ + #[Optional] + public ?string $favicon; + + /** + * Preview image URL if available. May be temporary or local-only to this device; download promptly if durable access is needed. + */ + #[Optional] + public ?string $img; + + /** + * Preview image dimensions. + */ + #[Optional] + public ?ImgSize $imgSize; + + /** + * Original URL when the displayed URL is shortened or redirected. + */ + #[Optional] + public ?string $originalURL; + + /** + * Link preview summary. + */ + #[Optional] + public ?string $summary; + + /** + * `new Link()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * Link::with(title: ..., url: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new Link)->withTitle(...)->withURL(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param ImgSize|ImgSizeShape|null $imgSize + */ + public static function with( + string $title, + string $url, + ?string $favicon = null, + ?string $img = null, + ImgSize|array|null $imgSize = null, + ?string $originalURL = null, + ?string $summary = null, + ): self { + $self = new self; + + $self['title'] = $title; + $self['url'] = $url; + + null !== $favicon && $self['favicon'] = $favicon; + null !== $img && $self['img'] = $img; + null !== $imgSize && $self['imgSize'] = $imgSize; + null !== $originalURL && $self['originalURL'] = $originalURL; + null !== $summary && $self['summary'] = $summary; + + return $self; + } + + /** + * Link preview title. + */ + public function withTitle(string $title): self + { + $self = clone $this; + $self['title'] = $title; + + return $self; + } + + /** + * Resolved link URL. + */ + public function withURL(string $url): self + { + $self = clone $this; + $self['url'] = $url; + + return $self; + } + + /** + * Favicon URL if available. May be temporary or local-only to this device; download promptly if durable access is needed. + */ + public function withFavicon(string $favicon): self + { + $self = clone $this; + $self['favicon'] = $favicon; + + return $self; + } + + /** + * Preview image URL if available. May be temporary or local-only to this device; download promptly if durable access is needed. + */ + public function withImg(string $img): self + { + $self = clone $this; + $self['img'] = $img; + + return $self; + } + + /** + * Preview image dimensions. + * + * @param ImgSize|ImgSizeShape $imgSize + */ + public function withImgSize(ImgSize|array $imgSize): self + { + $self = clone $this; + $self['imgSize'] = $imgSize; + + return $self; + } + + /** + * Original URL when the displayed URL is shortened or redirected. + */ + public function withOriginalURL(string $originalURL): self + { + $self = clone $this; + $self['originalURL'] = $originalURL; + + return $self; + } + + /** + * Link preview summary. + */ + public function withSummary(string $summary): self + { + $self = clone $this; + $self['summary'] = $summary; + + return $self; + } +} diff --git a/src/Message/Link/ImgSize.php b/src/Message/Link/ImgSize.php new file mode 100644 index 0000000..760bac4 --- /dev/null +++ b/src/Message/Link/ImgSize.php @@ -0,0 +1,62 @@ + */ + use SdkModel; + + #[Optional] + public ?float $height; + + #[Optional] + public ?float $width; + + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with(?float $height = null, ?float $width = null): self + { + $self = new self; + + null !== $height && $self['height'] = $height; + null !== $width && $self['width'] = $width; + + return $self; + } + + public function withHeight(float $height): self + { + $self = clone $this; + $self['height'] = $height; + + return $self; + } + + public function withWidth(float $width): self + { + $self = clone $this; + $self['width'] = $width; + + return $self; + } +} diff --git a/src/Message/Seen.php b/src/Message/Seen.php new file mode 100644 index 0000000..8a184c0 --- /dev/null +++ b/src/Message/Seen.php @@ -0,0 +1,34 @@ + + * @phpstan-type SeenShape = SeenVariants|array + */ +final class Seen implements ConverterSource +{ + use SdkUnion; + + /** + * @return list|array + */ + public static function variants(): array + { + return [ + 'bool', '\DateTimeInterface', new MapOf(MessageSeenByParticipant::class), + ]; + } +} diff --git a/src/Message/Seen/MessageSeenByParticipant.php b/src/Message/Seen/MessageSeenByParticipant.php new file mode 100644 index 0000000..720a57d --- /dev/null +++ b/src/Message/Seen/MessageSeenByParticipant.php @@ -0,0 +1,28 @@ +|array + */ + public static function variants(): array + { + return ['bool', '\DateTimeInterface']; + } +} diff --git a/src/Message/SendStatus.php b/src/Message/SendStatus.php new file mode 100644 index 0000000..8e93b4c --- /dev/null +++ b/src/Message/SendStatus.php @@ -0,0 +1,187 @@ +, + * timestamp: \DateTimeInterface, + * deliveredToUsers?: list|null, + * internalError?: string|null, + * message?: string|null, + * reason?: string|null, + * } + */ +final class SendStatus implements BaseModel +{ + /** @use SdkModel */ + use SdkModel; + + /** + * Current status of the message send attempt. + * + * @var value-of $status + */ + #[Required(enum: Status::class)] + public string $status; + + /** + * Timestamp for the send status event. + */ + #[Required] + public \DateTimeInterface $timestamp; + + /** + * User IDs the message was delivered to, when reported by the network. + * + * @var list|null $deliveredToUsers + */ + #[Optional(list: 'string')] + public ?array $deliveredToUsers; + + /** + * Internal bridge error detail. Intended for diagnostics, not end-user display. + */ + #[Optional] + public ?string $internalError; + + /** + * Human-readable send status or failure message. + */ + #[Optional] + public ?string $message; + + /** + * Machine-readable failure reason. Present when the send status is a failure. + */ + #[Optional] + public ?string $reason; + + /** + * `new SendStatus()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * SendStatus::with(status: ..., timestamp: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new SendStatus)->withStatus(...)->withTimestamp(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + * + * @param Status|value-of $status + * @param list|null $deliveredToUsers + */ + public static function with( + Status|string $status, + \DateTimeInterface $timestamp, + ?array $deliveredToUsers = null, + ?string $internalError = null, + ?string $message = null, + ?string $reason = null, + ): self { + $self = new self; + + $self['status'] = $status; + $self['timestamp'] = $timestamp; + + null !== $deliveredToUsers && $self['deliveredToUsers'] = $deliveredToUsers; + null !== $internalError && $self['internalError'] = $internalError; + null !== $message && $self['message'] = $message; + null !== $reason && $self['reason'] = $reason; + + return $self; + } + + /** + * Current status of the message send attempt. + * + * @param Status|value-of $status + */ + public function withStatus(Status|string $status): self + { + $self = clone $this; + $self['status'] = $status; + + return $self; + } + + /** + * Timestamp for the send status event. + */ + public function withTimestamp(\DateTimeInterface $timestamp): self + { + $self = clone $this; + $self['timestamp'] = $timestamp; + + return $self; + } + + /** + * User IDs the message was delivered to, when reported by the network. + * + * @param list $deliveredToUsers + */ + public function withDeliveredToUsers(array $deliveredToUsers): self + { + $self = clone $this; + $self['deliveredToUsers'] = $deliveredToUsers; + + return $self; + } + + /** + * Internal bridge error detail. Intended for diagnostics, not end-user display. + */ + public function withInternalError(string $internalError): self + { + $self = clone $this; + $self['internalError'] = $internalError; + + return $self; + } + + /** + * Human-readable send status or failure message. + */ + public function withMessage(string $message): self + { + $self = clone $this; + $self['message'] = $message; + + return $self; + } + + /** + * Machine-readable failure reason. Present when the send status is a failure. + */ + public function withReason(string $reason): self + { + $self = clone $this; + $self['reason'] = $reason; + + return $self; + } +} diff --git a/src/Message/SendStatus/Status.php b/src/Message/SendStatus/Status.php new file mode 100644 index 0000000..5c99941 --- /dev/null +++ b/src/Message/SendStatus/Status.php @@ -0,0 +1,19 @@ + */ + use SdkModel; + use SdkParams; + + /** + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + */ + #[Required] + public string $chatID; + + /** + * True to request deletion for everyone when the network supports it; false to delete only for the authenticated user when supported. + */ + #[Optional(nullable: true)] + public ?bool $forEveryone; + + /** + * `new MessageDeleteParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * MessageDeleteParams::with(chatID: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new MessageDeleteParams)->withChatID(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with(string $chatID, ?bool $forEveryone = null): self + { + $self = new self; + + $self['chatID'] = $chatID; + + null !== $forEveryone && $self['forEveryone'] = $forEveryone; + + return $self; + } + + /** + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + */ + public function withChatID(string $chatID): self + { + $self = clone $this; + $self['chatID'] = $chatID; + + return $self; + } + + /** + * True to request deletion for everyone when the network supports it; false to delete only for the authenticated user when supported. + */ + public function withForEveryone(?bool $forEveryone): self + { + $self = clone $this; + $self['forEveryone'] = $forEveryone; + + return $self; + } +} diff --git a/src/Messages/MessageRetrieveParams.php b/src/Messages/MessageRetrieveParams.php new file mode 100644 index 0000000..452ceec --- /dev/null +++ b/src/Messages/MessageRetrieveParams.php @@ -0,0 +1,74 @@ + */ + use SdkModel; + use SdkParams; + + /** + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + */ + #[Required] + public string $chatID; + + /** + * `new MessageRetrieveParams()` is missing required properties by the API. + * + * To enforce required parameters use + * ``` + * MessageRetrieveParams::with(chatID: ...) + * ``` + * + * Otherwise ensure the following setters are called + * + * ``` + * (new MessageRetrieveParams)->withChatID(...) + * ``` + */ + public function __construct() + { + $this->initialize(); + } + + /** + * Construct an instance from the required parameters. + * + * You must use named parameters to construct any parameters with a default value. + */ + public static function with(string $chatID): self + { + $self = new self; + + $self['chatID'] = $chatID; + + return $self; + } + + /** + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + */ + public function withChatID(string $chatID): self + { + $self = clone $this; + $self['chatID'] = $chatID; + + return $self; + } +} diff --git a/src/Messages/MessageSearchParams.php b/src/Messages/MessageSearchParams.php index e9ffe25..706245f 100644 --- a/src/Messages/MessageSearchParams.php +++ b/src/Messages/MessageSearchParams.php @@ -11,10 +11,9 @@ use BeeperDesktop\Messages\MessageSearchParams\ChatType; use BeeperDesktop\Messages\MessageSearchParams\Direction; use BeeperDesktop\Messages\MessageSearchParams\MediaType; -use BeeperDesktop\Messages\MessageSearchParams\Sender; /** - * Search messages across chats using Beeper's message index. + * Search messages across chats. * * @see BeeperDesktop\Services\MessagesService::search() * @@ -31,7 +30,7 @@ * limit?: int|null, * mediaTypes?: list>|null, * query?: string|null, - * sender?: string|null|Sender|value-of, + * sender?: string|null, * } */ final class MessageSearchParams implements BaseModel @@ -124,10 +123,8 @@ final class MessageSearchParams implements BaseModel /** * Filter by sender: 'me' (messages sent by the authenticated user), 'others' (messages sent by others), or a specific user ID string (user.id). - * - * @var string|value-of|null $sender */ - #[Optional(enum: Sender::class)] + #[Optional] public ?string $sender; public function __construct() @@ -145,7 +142,6 @@ public function __construct() * @param ChatType|value-of|null $chatType * @param Direction|value-of|null $direction * @param list>|null $mediaTypes - * @param string|Sender|value-of|null $sender */ public static function with( ?array $accountIDs = null, @@ -160,7 +156,7 @@ public static function with( ?int $limit = null, ?array $mediaTypes = null, ?string $query = null, - Sender|string|null $sender = null, + ?string $sender = null, ): self { $self = new self; @@ -325,10 +321,8 @@ public function withQuery(string $query): self /** * Filter by sender: 'me' (messages sent by the authenticated user), 'others' (messages sent by others), or a specific user ID string (user.id). - * - * @param string|Sender|value-of $sender */ - public function withSender(Sender|string $sender): self + public function withSender(string $sender): self { $self = clone $this; $self['sender'] = $sender; diff --git a/src/Messages/MessageSearchParams/Sender.php b/src/Messages/MessageSearchParams/Sender.php deleted file mode 100644 index e58a8ab..0000000 --- a/src/Messages/MessageSearchParams/Sender.php +++ /dev/null @@ -1,15 +0,0 @@ -|null $type */ @@ -174,7 +174,7 @@ public function withSize(Size|array $size): self } /** - * Special attachment type (gif, voiceNote, sticker). If omitted, auto-detected from mimeType. + * Attachment type hint (image, video, audio, file, gif, voice-note, sticker). If omitted, auto-detected from mimeType. * * @param Type|value-of $type */ diff --git a/src/Messages/MessageSendParams/Attachment/Type.php b/src/Messages/MessageSendParams/Attachment/Type.php index e11b46b..7f27b54 100644 --- a/src/Messages/MessageSendParams/Attachment/Type.php +++ b/src/Messages/MessageSendParams/Attachment/Type.php @@ -5,13 +5,21 @@ namespace BeeperDesktop\Messages\MessageSendParams\Attachment; /** - * Special attachment type (gif, voiceNote, sticker). If omitted, auto-detected from mimeType. + * Attachment type hint (image, video, audio, file, gif, voice-note, sticker). If omitted, auto-detected from mimeType. */ enum Type: string { + case IMAGE = 'image'; + + case VIDEO = 'video'; + + case AUDIO = 'audio'; + + case FILE = 'file'; + case GIF = 'gif'; - case VOICE_NOTE = 'voiceNote'; + case VOICE_NOTE = 'voice-note'; case STICKER = 'sticker'; } diff --git a/src/Messages/MessageSendResponse.php b/src/Messages/MessageSendResponse.php index e5cbdaa..b69de8b 100644 --- a/src/Messages/MessageSendResponse.php +++ b/src/Messages/MessageSendResponse.php @@ -19,13 +19,13 @@ final class MessageSendResponse implements BaseModel use SdkModel; /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ #[Required] public string $chatID; /** - * Pending message ID. + * Pending ID assigned to the message before the network confirms the send. Pass it to GET /v1/chats/{chatID}/messages/{messageID} to resolve, or wait for the matching message.upserted over the WebSocket. */ #[Required] public string $pendingMessageID; @@ -65,7 +65,7 @@ public static function with(string $chatID, string $pendingMessageID): self } /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ public function withChatID(string $chatID): self { @@ -76,7 +76,7 @@ public function withChatID(string $chatID): self } /** - * Pending message ID. + * Pending ID assigned to the message before the network confirms the send. Pass it to GET /v1/chats/{chatID}/messages/{messageID} to resolve, or wait for the matching message.upserted over the WebSocket. */ public function withPendingMessageID(string $pendingMessageID): self { diff --git a/src/Messages/MessageUpdateParams.php b/src/Messages/MessageUpdateParams.php index ccca067..9be3199 100644 --- a/src/Messages/MessageUpdateParams.php +++ b/src/Messages/MessageUpdateParams.php @@ -23,7 +23,7 @@ final class MessageUpdateParams implements BaseModel use SdkParams; /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ #[Required] public string $chatID; @@ -69,7 +69,7 @@ public static function with(string $chatID, string $text): self } /** - * Unique identifier of the chat. + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ public function withChatID(string $chatID): self { diff --git a/src/Messages/MessageUpdateResponse.php b/src/Messages/MessageUpdateResponse.php index ad4b0ee..5fc82fd 100644 --- a/src/Messages/MessageUpdateResponse.php +++ b/src/Messages/MessageUpdateResponse.php @@ -4,13 +4,49 @@ namespace BeeperDesktop\Messages; +use BeeperDesktop\Attachment; +use BeeperDesktop\Core\Attributes\Optional; use BeeperDesktop\Core\Attributes\Required; use BeeperDesktop\Core\Concerns\SdkModel; use BeeperDesktop\Core\Contracts\BaseModel; +use BeeperDesktop\Message\Link; +use BeeperDesktop\Message\Seen; +use BeeperDesktop\Message\SendStatus; +use BeeperDesktop\Message\Type; +use BeeperDesktop\Reaction; /** + * @phpstan-import-type SeenVariants from \BeeperDesktop\Message\Seen + * @phpstan-import-type AttachmentShape from \BeeperDesktop\Attachment + * @phpstan-import-type LinkShape from \BeeperDesktop\Message\Link + * @phpstan-import-type ReactionShape from \BeeperDesktop\Reaction + * @phpstan-import-type SeenShape from \BeeperDesktop\Message\Seen + * @phpstan-import-type SendStatusShape from \BeeperDesktop\Message\SendStatus + * * @phpstan-type MessageUpdateResponseShape = array{ - * chatID: string, messageID: string, success: bool + * id: string, + * accountID: string, + * chatID: string, + * senderID: string, + * sortKey: string, + * timestamp: \DateTimeInterface, + * attachments?: list|null, + * editedTimestamp?: \DateTimeInterface|null, + * isDeleted?: bool|null, + * isHidden?: bool|null, + * isSender?: bool|null, + * isUnread?: bool|null, + * linkedMessageID?: string|null, + * links?: list|null, + * mentions?: list|null, + * reactions?: list|null, + * seen?: SeenShape|null, + * senderName?: string|null, + * sendStatus?: null|SendStatus|SendStatusShape, + * text?: string|null, + * type?: null|Type|value-of, + * messageID: string, + * success: bool, * } */ final class MessageUpdateResponse implements BaseModel @@ -19,19 +55,155 @@ final class MessageUpdateResponse implements BaseModel use SdkModel; /** - * Unique identifier of the chat. + * Message ID. + */ + #[Required] + public string $id; + + /** + * Beeper account ID the message belongs to. + */ + #[Required] + public string $accountID; + + /** + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ #[Required] public string $chatID; /** - * Message ID. + * Matrix-style fully-qualified sender user ID, usually including a bridge prefix and homeserver. + */ + #[Required] + public string $senderID; + + /** + * A unique, sortable key used to sort messages. + */ + #[Required] + public string $sortKey; + + /** + * Message timestamp. + */ + #[Required] + public \DateTimeInterface $timestamp; + + /** + * Attachments included with this message, if any. + * + * @var list|null $attachments + */ + #[Optional(list: Attachment::class)] + public ?array $attachments; + + /** + * Timestamp when the message was edited, if known. + */ + #[Optional] + public ?\DateTimeInterface $editedTimestamp; + + /** + * True if the message has been deleted. + */ + #[Optional] + public ?bool $isDeleted; + + /** + * True if the message is hidden from normal display. + */ + #[Optional] + public ?bool $isHidden; + + /** + * True if the authenticated user sent the message. + */ + #[Optional] + public ?bool $isSender; + + /** + * True if the message is unread for the authenticated user. May be omitted. + */ + #[Optional] + public ?bool $isUnread; + + /** + * ID of the message this is a reply to, if any. + */ + #[Optional] + public ?string $linkedMessageID; + + /** + * Link previews included with this message, if any. + * + * @var list|null $links + */ + #[Optional(list: Link::class)] + public ?array $links; + + /** + * Mentioned user IDs, @room, or null for legacy messages that require text scanning. + * + * @var list|null $mentions + */ + #[Optional(list: 'string', nullable: true)] + public ?array $mentions; + + /** + * Reactions to the message, if any. + * + * @var list|null $reactions + */ + #[Optional(list: Reaction::class)] + public ?array $reactions; + + /** + * Read receipt state for this message, when available. + * + * @var SeenVariants|null $seen + */ + #[Optional(union: Seen::class)] + public bool|\DateTimeInterface|array|null $seen; + + /** + * Resolved sender display name (impersonator/full name/username/participant name). + */ + #[Optional] + public ?string $senderName; + + /** + * Message send status for this message, when reported by the bridge. + */ + #[Optional] + public ?SendStatus $sendStatus; + + /** + * Matrix HTML body if present. + */ + #[Optional] + public ?string $text; + + /** + * Message content type. Useful for distinguishing reactions, media messages, and state events from regular text messages. + * + * @var value-of|null $type + */ + #[Optional(enum: Type::class)] + public ?string $type; + + /** + * @deprecated + * + * DEPRECATED - use id instead. Compatibility alias for older clients. */ #[Required] public string $messageID; /** - * Whether the message was successfully edited. + * @deprecated + * + * DEPRECATED - compatibility field. Successful responses are already represented by the 200 status code. */ #[Required] public bool $success; @@ -41,14 +213,28 @@ final class MessageUpdateResponse implements BaseModel * * To enforce required parameters use * ``` - * MessageUpdateResponse::with(chatID: ..., messageID: ..., success: ...) + * MessageUpdateResponse::with( + * id: ..., + * accountID: ..., + * chatID: ..., + * senderID: ..., + * sortKey: ..., + * timestamp: ..., + * messageID: ..., + * success: ..., + * ) * ``` * * Otherwise ensure the following setters are called * * ``` * (new MessageUpdateResponse) + * ->withID(...) + * ->withAccountID(...) * ->withChatID(...) + * ->withSenderID(...) + * ->withSortKey(...) + * ->withTimestamp(...) * ->withMessageID(...) * ->withSuccess(...) * ``` @@ -62,23 +248,94 @@ public function __construct() * Construct an instance from the required parameters. * * You must use named parameters to construct any parameters with a default value. + * + * @param list|null $attachments + * @param list|null $links + * @param list|null $mentions + * @param list|null $reactions + * @param SeenShape|null $seen + * @param SendStatus|SendStatusShape|null $sendStatus + * @param Type|value-of|null $type */ public static function with( + string $id, + string $accountID, string $chatID, + string $senderID, + string $sortKey, + \DateTimeInterface $timestamp, string $messageID, - bool $success + bool $success, + ?array $attachments = null, + ?\DateTimeInterface $editedTimestamp = null, + ?bool $isDeleted = null, + ?bool $isHidden = null, + ?bool $isSender = null, + ?bool $isUnread = null, + ?string $linkedMessageID = null, + ?array $links = null, + ?array $mentions = null, + ?array $reactions = null, + bool|\DateTimeInterface|array|null $seen = null, + ?string $senderName = null, + SendStatus|array|null $sendStatus = null, + ?string $text = null, + Type|string|null $type = null, ): self { $self = new self; + $self['id'] = $id; + $self['accountID'] = $accountID; $self['chatID'] = $chatID; + $self['senderID'] = $senderID; + $self['sortKey'] = $sortKey; + $self['timestamp'] = $timestamp; $self['messageID'] = $messageID; $self['success'] = $success; + null !== $attachments && $self['attachments'] = $attachments; + null !== $editedTimestamp && $self['editedTimestamp'] = $editedTimestamp; + null !== $isDeleted && $self['isDeleted'] = $isDeleted; + null !== $isHidden && $self['isHidden'] = $isHidden; + null !== $isSender && $self['isSender'] = $isSender; + null !== $isUnread && $self['isUnread'] = $isUnread; + null !== $linkedMessageID && $self['linkedMessageID'] = $linkedMessageID; + null !== $links && $self['links'] = $links; + null !== $mentions && $self['mentions'] = $mentions; + null !== $reactions && $self['reactions'] = $reactions; + null !== $seen && $self['seen'] = $seen; + null !== $senderName && $self['senderName'] = $senderName; + null !== $sendStatus && $self['sendStatus'] = $sendStatus; + null !== $text && $self['text'] = $text; + null !== $type && $self['type'] = $type; + return $self; } /** - * Unique identifier of the chat. + * Message ID. + */ + public function withID(string $id): self + { + $self = clone $this; + $self['id'] = $id; + + return $self; + } + + /** + * Beeper account ID the message belongs to. + */ + public function withAccountID(string $accountID): self + { + $self = clone $this; + $self['accountID'] = $accountID; + + return $self; + } + + /** + * Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. */ public function withChatID(string $chatID): self { @@ -89,7 +346,220 @@ public function withChatID(string $chatID): self } /** - * Message ID. + * Matrix-style fully-qualified sender user ID, usually including a bridge prefix and homeserver. + */ + public function withSenderID(string $senderID): self + { + $self = clone $this; + $self['senderID'] = $senderID; + + return $self; + } + + /** + * A unique, sortable key used to sort messages. + */ + public function withSortKey(string $sortKey): self + { + $self = clone $this; + $self['sortKey'] = $sortKey; + + return $self; + } + + /** + * Message timestamp. + */ + public function withTimestamp(\DateTimeInterface $timestamp): self + { + $self = clone $this; + $self['timestamp'] = $timestamp; + + return $self; + } + + /** + * Attachments included with this message, if any. + * + * @param list $attachments + */ + public function withAttachments(array $attachments): self + { + $self = clone $this; + $self['attachments'] = $attachments; + + return $self; + } + + /** + * Timestamp when the message was edited, if known. + */ + public function withEditedTimestamp( + \DateTimeInterface $editedTimestamp + ): self { + $self = clone $this; + $self['editedTimestamp'] = $editedTimestamp; + + return $self; + } + + /** + * True if the message has been deleted. + */ + public function withIsDeleted(bool $isDeleted): self + { + $self = clone $this; + $self['isDeleted'] = $isDeleted; + + return $self; + } + + /** + * True if the message is hidden from normal display. + */ + public function withIsHidden(bool $isHidden): self + { + $self = clone $this; + $self['isHidden'] = $isHidden; + + return $self; + } + + /** + * True if the authenticated user sent the message. + */ + public function withIsSender(bool $isSender): self + { + $self = clone $this; + $self['isSender'] = $isSender; + + return $self; + } + + /** + * True if the message is unread for the authenticated user. May be omitted. + */ + public function withIsUnread(bool $isUnread): self + { + $self = clone $this; + $self['isUnread'] = $isUnread; + + return $self; + } + + /** + * ID of the message this is a reply to, if any. + */ + public function withLinkedMessageID(string $linkedMessageID): self + { + $self = clone $this; + $self['linkedMessageID'] = $linkedMessageID; + + return $self; + } + + /** + * Link previews included with this message, if any. + * + * @param list $links + */ + public function withLinks(array $links): self + { + $self = clone $this; + $self['links'] = $links; + + return $self; + } + + /** + * Mentioned user IDs, @room, or null for legacy messages that require text scanning. + * + * @param list|null $mentions + */ + public function withMentions(?array $mentions): self + { + $self = clone $this; + $self['mentions'] = $mentions; + + return $self; + } + + /** + * Reactions to the message, if any. + * + * @param list $reactions + */ + public function withReactions(array $reactions): self + { + $self = clone $this; + $self['reactions'] = $reactions; + + return $self; + } + + /** + * Read receipt state for this message, when available. + * + * @param SeenShape $seen + */ + public function withSeen(bool|\DateTimeInterface|array $seen): self + { + $self = clone $this; + $self['seen'] = $seen; + + return $self; + } + + /** + * Resolved sender display name (impersonator/full name/username/participant name). + */ + public function withSenderName(string $senderName): self + { + $self = clone $this; + $self['senderName'] = $senderName; + + return $self; + } + + /** + * Message send status for this message, when reported by the bridge. + * + * @param SendStatus|SendStatusShape $sendStatus + */ + public function withSendStatus(SendStatus|array $sendStatus): self + { + $self = clone $this; + $self['sendStatus'] = $sendStatus; + + return $self; + } + + /** + * Matrix HTML body if present. + */ + public function withText(string $text): self + { + $self = clone $this; + $self['text'] = $text; + + return $self; + } + + /** + * Message content type. Useful for distinguishing reactions, media messages, and state events from regular text messages. + * + * @param Type|value-of $type + */ + public function withType(Type|string $type): self + { + $self = clone $this; + $self['type'] = $type; + + return $self; + } + + /** + * DEPRECATED - use id instead. Compatibility alias for older clients. */ public function withMessageID(string $messageID): self { @@ -100,7 +570,7 @@ public function withMessageID(string $messageID): self } /** - * Whether the message was successfully edited. + * DEPRECATED - compatibility field. Successful responses are already represented by the 200 status code. */ public function withSuccess(bool $success): self { diff --git a/src/Reaction.php b/src/Reaction.php index a44defc..dd5bfd0 100644 --- a/src/Reaction.php +++ b/src/Reaction.php @@ -24,7 +24,7 @@ final class Reaction implements BaseModel use SdkModel; /** - * Reaction ID, typically ${participantID}${reactionKey} if multiple reactions allowed, or just participantID otherwise. + * Reaction ID. When a participant can react more than once, the ID is the participant ID concatenated with the reaction key; otherwise it equals the participant ID. */ #[Required] public string $id; @@ -97,7 +97,7 @@ public static function with( } /** - * Reaction ID, typically ${participantID}${reactionKey} if multiple reactions allowed, or just participantID otherwise. + * Reaction ID. When a participant can react more than once, the ID is the participant ID concatenated with the reaction key; otherwise it equals the participant ID. */ public function withID(string $id): self { diff --git a/src/ServiceContracts/AssetsContract.php b/src/ServiceContracts/AssetsContract.php index 6064d5b..325d0ce 100644 --- a/src/ServiceContracts/AssetsContract.php +++ b/src/ServiceContracts/AssetsContract.php @@ -8,6 +8,7 @@ use BeeperDesktop\Assets\AssetUploadBase64Response; use BeeperDesktop\Assets\AssetUploadResponse; use BeeperDesktop\Core\Exceptions\APIException; +use BeeperDesktop\Core\FileParam; use BeeperDesktop\RequestOptions; /** @@ -18,7 +19,7 @@ interface AssetsContract /** * @api * - * @param string $url matrix content URL (mxc:// or localmxc://) for the asset to download + * @param string $url matrix content URL (mxc:// or localmxc://) for the file to download * @param RequestOpts|null $requestOptions * * @throws APIException @@ -31,7 +32,7 @@ public function download( /** * @api * - * @param string $url Asset URL to serve. Accepts mxc://, localmxc://, or file:// URLs. + * @param string $url File URL to serve. Accepts mxc://, localmxc://, or file:// URLs. * @param RequestOpts|null $requestOptions * * @throws APIException @@ -39,12 +40,12 @@ public function download( public function serve( string $url, RequestOptions|array|null $requestOptions = null - ): mixed; + ): string; /** * @api * - * @param string $file the file to upload (max 500 MB) + * @param string|FileParam $file the file to upload (max 500 MB) * @param string $fileName Original filename. Defaults to the uploaded file name if omitted * @param string $mimeType MIME type. Auto-detected from magic bytes if omitted * @param RequestOpts|null $requestOptions @@ -52,7 +53,7 @@ public function serve( * @throws APIException */ public function upload( - string $file, + string|FileParam $file, ?string $fileName = null, ?string $mimeType = null, RequestOptions|array|null $requestOptions = null, diff --git a/src/ServiceContracts/AssetsRawContract.php b/src/ServiceContracts/AssetsRawContract.php index 2e7e75d..b438606 100644 --- a/src/ServiceContracts/AssetsRawContract.php +++ b/src/ServiceContracts/AssetsRawContract.php @@ -41,7 +41,7 @@ public function download( * @param array|AssetServeParams $params * @param RequestOpts|null $requestOptions * - * @return BaseResponse + * @return BaseResponse * * @throws APIException */ diff --git a/src/ServiceContracts/BeeperDesktopClientContract.php b/src/ServiceContracts/BeeperDesktopClientContract.php index d15413f..5886c42 100644 --- a/src/ServiceContracts/BeeperDesktopClientContract.php +++ b/src/ServiceContracts/BeeperDesktopClientContract.php @@ -18,8 +18,8 @@ interface BeeperDesktopClientContract * @api * * @param string $chatID Optional Beeper chat ID (or local chat ID) to focus after opening the app. If omitted, only opens/focuses the app. - * @param string $draftAttachmentPath optional draft attachment path to populate in the message input field - * @param string $draftText optional draft text to populate in the message input field + * @param string $draftAttachmentPath optional image path to populate in the message input field + * @param string $draftText optional plain text to populate in the message input field * @param string $messageID Optional message ID. Jumps to that message in the chat when opening. * @param RequestOpts|null $requestOptions * diff --git a/src/ServiceContracts/Chats/Messages/ReactionsContract.php b/src/ServiceContracts/Chats/Messages/ReactionsContract.php index df6d53e..aad62f2 100644 --- a/src/ServiceContracts/Chats/Messages/ReactionsContract.php +++ b/src/ServiceContracts/Chats/Messages/ReactionsContract.php @@ -17,27 +17,27 @@ interface ReactionsContract /** * @api * - * @param string $messageID Path param: ID of the message to remove a reaction from - * @param string $chatID path param: Unique identifier of the chat - * @param string $reactionKey Query param: Reaction key to remove + * @param string $reactionKey Reaction key to remove (emoji, shortcode, or custom emoji key) + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param string $messageID message ID * @param RequestOpts|null $requestOptions * * @throws APIException */ public function delete( - string $messageID, - string $chatID, string $reactionKey, + string $chatID, + string $messageID, RequestOptions|array|null $requestOptions = null, ): ReactionDeleteResponse; /** * @api * - * @param string $messageID Path param: ID of the message to add a reaction to - * @param string $chatID path param: Unique identifier of the chat + * @param string $messageID path param: Message ID + * @param string $chatID Path param: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param string $reactionKey Body param: Reaction key to add (emoji, shortcode, or custom emoji key) - * @param string $transactionID Body param: Optional transaction ID for deduplication and local echo tracking + * @param string $transactionID Body param: Optional transaction ID for deduplication and send tracking * @param RequestOpts|null $requestOptions * * @throws APIException diff --git a/src/ServiceContracts/Chats/Messages/ReactionsRawContract.php b/src/ServiceContracts/Chats/Messages/ReactionsRawContract.php index 463e43f..318d31a 100644 --- a/src/ServiceContracts/Chats/Messages/ReactionsRawContract.php +++ b/src/ServiceContracts/Chats/Messages/ReactionsRawContract.php @@ -20,7 +20,7 @@ interface ReactionsRawContract /** * @api * - * @param string $messageID Path param: ID of the message to remove a reaction from + * @param string $reactionKey Reaction key to remove (emoji, shortcode, or custom emoji key) * @param array|ReactionDeleteParams $params * @param RequestOpts|null $requestOptions * @@ -29,7 +29,7 @@ interface ReactionsRawContract * @throws APIException */ public function delete( - string $messageID, + string $reactionKey, array|ReactionDeleteParams $params, RequestOptions|array|null $requestOptions = null, ): BaseResponse; @@ -37,7 +37,7 @@ public function delete( /** * @api * - * @param string $messageID Path param: ID of the message to add a reaction to + * @param string $messageID path param: Message ID * @param array|ReactionAddParams $params * @param RequestOpts|null $requestOptions * diff --git a/src/ServiceContracts/Chats/RemindersContract.php b/src/ServiceContracts/Chats/RemindersContract.php index ee868dd..1e66144 100644 --- a/src/ServiceContracts/Chats/RemindersContract.php +++ b/src/ServiceContracts/Chats/RemindersContract.php @@ -17,7 +17,7 @@ interface RemindersContract /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param Reminder|ReminderShape $reminder Reminder configuration * @param RequestOpts|null $requestOptions * @@ -32,7 +32,7 @@ public function create( /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param RequestOpts|null $requestOptions * * @throws APIException diff --git a/src/ServiceContracts/Chats/RemindersRawContract.php b/src/ServiceContracts/Chats/RemindersRawContract.php index ba48a77..dbdea2a 100644 --- a/src/ServiceContracts/Chats/RemindersRawContract.php +++ b/src/ServiceContracts/Chats/RemindersRawContract.php @@ -17,7 +17,7 @@ interface RemindersRawContract /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array|ReminderCreateParams $params * @param RequestOpts|null $requestOptions * @@ -34,7 +34,7 @@ public function create( /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param RequestOpts|null $requestOptions * * @return BaseResponse diff --git a/src/ServiceContracts/ChatsContract.php b/src/ServiceContracts/ChatsContract.php index e86488b..eeed273 100644 --- a/src/ServiceContracts/ChatsContract.php +++ b/src/ServiceContracts/ChatsContract.php @@ -4,20 +4,24 @@ namespace BeeperDesktop\ServiceContracts; -use BeeperDesktop\Chats\ChatCreateParams\Chat; +use BeeperDesktop\Chats\Chat; +use BeeperDesktop\Chats\ChatCreateParams\Type; use BeeperDesktop\Chats\ChatListParams\Direction; use BeeperDesktop\Chats\ChatListResponse; use BeeperDesktop\Chats\ChatNewResponse; use BeeperDesktop\Chats\ChatSearchParams\Inbox; use BeeperDesktop\Chats\ChatSearchParams\Scope; -use BeeperDesktop\Chats\ChatSearchParams\Type; +use BeeperDesktop\Chats\ChatStartParams\User; +use BeeperDesktop\Chats\ChatStartResponse; +use BeeperDesktop\Chats\ChatUpdateParams\Draft; use BeeperDesktop\Core\Exceptions\APIException; use BeeperDesktop\CursorNoLimit; use BeeperDesktop\CursorSearch; use BeeperDesktop\RequestOptions; /** - * @phpstan-import-type ChatShape from \BeeperDesktop\Chats\ChatCreateParams\Chat + * @phpstan-import-type DraftShape from \BeeperDesktop\Chats\ChatUpdateParams\Draft + * @phpstan-import-type UserShape from \BeeperDesktop\Chats\ChatStartParams\User * @phpstan-import-type RequestOpts from \BeeperDesktop\RequestOptions */ interface ChatsContract @@ -25,30 +29,69 @@ interface ChatsContract /** * @api * - * @param Chat|ChatShape $chat + * @param string $accountID account to create or start the chat on + * @param list $participantIDs user IDs to include in the new chat + * @param Type|value-of $type 'single' requires exactly one participantID; 'group' supports multiple participants and optional title + * @param string $messageText optional first message content if the platform requires it to create the chat + * @param string $title optional title for group chats; ignored for single chats on most networks * @param RequestOpts|null $requestOptions * * @throws APIException */ public function create( - Chat|array $chat, - RequestOptions|array|null $requestOptions = null + string $accountID, + array $participantIDs, + Type|string $type, + ?string $messageText = null, + ?string $title = null, + RequestOptions|array|null $requestOptions = null, ): ChatNewResponse; /** * @api * - * @param string $chatID unique identifier of the chat - * @param int|null $maxParticipantCount Maximum number of participants to return. Use -1 for all; otherwise 0–500. Defaults to all (-1). + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param int|null $maxParticipantCount Maximum number of participants to return. Use -1 for all; otherwise 0-500. Defaults to 100. List and search endpoints return up to 20 participants per chat. * @param RequestOpts|null $requestOptions * * @throws APIException */ public function retrieve( string $chatID, - ?int $maxParticipantCount = -1, + ?int $maxParticipantCount = 100, RequestOptions|array|null $requestOptions = null, - ): \BeeperDesktop\Chats\Chat; + ): Chat; + + /** + * @api + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param string|null $description Group chat description/topic. Support depends on the chat account and chat permissions. + * @param Draft|DraftShape|null $draft Draft object to set or clear. Non-empty drafts are only accepted when the current draft is empty. Send draft=null to clear text and attachments together before setting a new draft. + * @param string|null $imgURL Local filesystem path to a group chat avatar image. Support depends on the chat account and chat permissions. + * @param bool $isArchived archive or unarchive the chat + * @param bool $isLowPriority mark or unmark the chat as low priority when supported by the account + * @param bool $isMuted mute or unmute the chat + * @param bool $isPinned pin or unpin the chat when supported by the account + * @param int|null $messageExpirySeconds disappearing-message timer in seconds, or null to clear when supported + * @param string|null $title Custom chat title. Support depends on the chat account and chat permissions. + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function update( + string $chatID, + ?string $description = null, + Draft|array|null $draft = null, + ?string $imgURL = null, + ?bool $isArchived = null, + ?bool $isLowPriority = null, + ?bool $isMuted = null, + ?bool $isPinned = null, + ?int $messageExpirySeconds = null, + ?string $title = null, + RequestOptions|array|null $requestOptions = null, + ): Chat; /** * @api @@ -72,7 +115,7 @@ public function list( /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param bool $archived True to archive, false to unarchive * @param RequestOpts|null $requestOptions * @@ -84,6 +127,49 @@ public function archive( RequestOptions|array|null $requestOptions = null, ): mixed; + /** + * @api + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param string $messageID optional message ID to mark read through + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function markRead( + string $chatID, + ?string $messageID = null, + RequestOptions|array|null $requestOptions = null, + ): Chat; + + /** + * @api + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param string $messageID optional message ID to mark unread from + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function markUnread( + string $chatID, + ?string $messageID = null, + RequestOptions|array|null $requestOptions = null, + ): Chat; + + /** + * @api + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function notifyAnyway( + string $chatID, + RequestOptions|array|null $requestOptions = null + ): Chat; + /** * @api * @@ -97,11 +183,11 @@ public function archive( * @param int $limit Set the maximum number of chats to retrieve. Valid range: 1-200, default is 50 * @param string $query Literal token search (non-semantic). Use single words users type (e.g., "dinner"). When multiple words provided, ALL must match. Case-insensitive. * @param Scope|value-of $scope search scope: 'titles' matches title + network; 'participants' matches participant names - * @param Type|value-of $type Specify the type of chats to retrieve: use "single" for direct messages, "group" for group chats, or "any" to get all types + * @param \BeeperDesktop\Chats\ChatSearchParams\Type|value-of<\BeeperDesktop\Chats\ChatSearchParams\Type> $type Specify the type of chats to retrieve: use "single" for direct messages, "group" for group chats, or "any" to get all types * @param bool|null $unreadOnly Set to true to only retrieve chats that have unread messages * @param RequestOpts|null $requestOptions * - * @return CursorSearch<\BeeperDesktop\Chats\Chat> + * @return CursorSearch * * @throws APIException */ @@ -116,8 +202,27 @@ public function search( int $limit = 50, ?string $query = null, Scope|string $scope = 'titles', - Type|string $type = 'any', + \BeeperDesktop\Chats\ChatSearchParams\Type|string $type = 'any', ?bool $unreadOnly = null, RequestOptions|array|null $requestOptions = null, ): CursorSearch; + + /** + * @api + * + * @param string $accountID account to create or start the chat on + * @param User|UserShape $user merged user-like contact payload used to resolve the best identifier + * @param bool $allowInvite whether invite-based DM creation is allowed when required by the platform + * @param string $messageText optional first message content if the platform requires it to create the chat + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function start( + string $accountID, + User|array $user, + bool $allowInvite = true, + ?string $messageText = null, + RequestOptions|array|null $requestOptions = null, + ): ChatStartResponse; } diff --git a/src/ServiceContracts/ChatsRawContract.php b/src/ServiceContracts/ChatsRawContract.php index 70e29b1..56770f6 100644 --- a/src/ServiceContracts/ChatsRawContract.php +++ b/src/ServiceContracts/ChatsRawContract.php @@ -9,9 +9,14 @@ use BeeperDesktop\Chats\ChatCreateParams; use BeeperDesktop\Chats\ChatListParams; use BeeperDesktop\Chats\ChatListResponse; +use BeeperDesktop\Chats\ChatMarkReadParams; +use BeeperDesktop\Chats\ChatMarkUnreadParams; use BeeperDesktop\Chats\ChatNewResponse; use BeeperDesktop\Chats\ChatRetrieveParams; use BeeperDesktop\Chats\ChatSearchParams; +use BeeperDesktop\Chats\ChatStartParams; +use BeeperDesktop\Chats\ChatStartResponse; +use BeeperDesktop\Chats\ChatUpdateParams; use BeeperDesktop\Core\Contracts\BaseResponse; use BeeperDesktop\Core\Exceptions\APIException; use BeeperDesktop\CursorNoLimit; @@ -41,7 +46,7 @@ public function create( /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array|ChatRetrieveParams $params * @param RequestOpts|null $requestOptions * @@ -55,6 +60,23 @@ public function retrieve( RequestOptions|array|null $requestOptions = null, ): BaseResponse; + /** + * @api + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param array|ChatUpdateParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function update( + string $chatID, + array|ChatUpdateParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse; + /** * @api * @@ -73,7 +95,7 @@ public function list( /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array|ChatArchiveParams $params * @param RequestOpts|null $requestOptions * @@ -87,6 +109,55 @@ public function archive( RequestOptions|array|null $requestOptions = null, ): BaseResponse; + /** + * @api + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param array|ChatMarkReadParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function markRead( + string $chatID, + array|ChatMarkReadParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse; + + /** + * @api + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param array|ChatMarkUnreadParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function markUnread( + string $chatID, + array|ChatMarkUnreadParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse; + + /** + * @api + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function notifyAnyway( + string $chatID, + RequestOptions|array|null $requestOptions = null + ): BaseResponse; + /** * @api * @@ -101,4 +172,19 @@ public function search( array|ChatSearchParams $params, RequestOptions|array|null $requestOptions = null, ): BaseResponse; + + /** + * @api + * + * @param array|ChatStartParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function start( + array|ChatStartParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse; } diff --git a/src/ServiceContracts/MessagesContract.php b/src/ServiceContracts/MessagesContract.php index 886634d..c35650c 100644 --- a/src/ServiceContracts/MessagesContract.php +++ b/src/ServiceContracts/MessagesContract.php @@ -5,13 +5,12 @@ namespace BeeperDesktop\ServiceContracts; use BeeperDesktop\Core\Exceptions\APIException; +use BeeperDesktop\CursorNoLimit; use BeeperDesktop\CursorSearch; -use BeeperDesktop\CursorSortKey; use BeeperDesktop\Message; use BeeperDesktop\Messages\MessageListParams\Direction; use BeeperDesktop\Messages\MessageSearchParams\ChatType; use BeeperDesktop\Messages\MessageSearchParams\MediaType; -use BeeperDesktop\Messages\MessageSearchParams\Sender; use BeeperDesktop\Messages\MessageSendParams\Attachment; use BeeperDesktop\Messages\MessageSendResponse; use BeeperDesktop\Messages\MessageUpdateResponse; @@ -26,8 +25,23 @@ interface MessagesContract /** * @api * - * @param string $messageID Path param: ID of the message to edit - * @param string $chatID path param: Unique identifier of the chat + * @param string $messageID message ID + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function retrieve( + string $messageID, + string $chatID, + RequestOptions|array|null $requestOptions = null, + ): Message; + + /** + * @api + * + * @param string $messageID path param: Message ID + * @param string $chatID Path param: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param string $text Body param: New text content for the message * @param RequestOpts|null $requestOptions * @@ -43,12 +57,12 @@ public function update( /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param string $cursor Opaque pagination cursor; do not inspect. Use together with 'direction'. * @param Direction|value-of $direction Pagination direction used with 'cursor': 'before' fetches older results, 'after' fetches newer results. Defaults to 'before' when only 'cursor' is provided. * @param RequestOpts|null $requestOptions * - * @return CursorSortKey + * @return CursorNoLimit * * @throws APIException */ @@ -57,7 +71,24 @@ public function list( ?string $cursor = null, Direction|string|null $direction = null, RequestOptions|array|null $requestOptions = null, - ): CursorSortKey; + ): CursorNoLimit; + + /** + * @api + * + * @param string $messageID path param: Message ID + * @param string $chatID Path param: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param bool|null $forEveryone query param: True to request deletion for everyone when the network supports it; false to delete only for the authenticated user when supported + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function delete( + string $messageID, + string $chatID, + ?bool $forEveryone = true, + RequestOptions|array|null $requestOptions = null, + ): mixed; /** * @api @@ -74,7 +105,7 @@ public function list( * @param int $limit maximum number of messages to return * @param list> $mediaTypes Filter messages by media types. Use ['any'] for any media type, or specify exact types like ['video', 'image']. Omit for no media filtering. * @param string $query Literal word search (non-semantic). Finds messages containing these EXACT words in any order. Use single words users actually type, not concepts or phrases. Example: use "dinner" not "dinner plans", use "sick" not "health issues". If omitted, returns results filtered only by other parameters. - * @param string|Sender|value-of $sender Filter by sender: 'me' (messages sent by the authenticated user), 'others' (messages sent by others), or a specific user ID string (user.id). + * @param string $sender Filter by sender: 'me' (messages sent by the authenticated user), 'others' (messages sent by others), or a specific user ID string (user.id). * @param RequestOpts|null $requestOptions * * @return CursorSearch @@ -94,17 +125,17 @@ public function search( int $limit = 20, ?array $mediaTypes = null, ?string $query = null, - Sender|string|null $sender = null, + ?string $sender = null, RequestOptions|array|null $requestOptions = null, ): CursorSearch; /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param Attachment|AttachmentShape $attachment Single attachment to send with the message * @param string $replyToMessageID Provide a message ID to send this as a reply to an existing message - * @param string $text Text content of the message you want to send. You may use markdown. + * @param string $text Draft text. Plain text and Markdown are converted to Matrix HTML with the same rules used by send and edit. * @param RequestOpts|null $requestOptions * * @throws APIException diff --git a/src/ServiceContracts/MessagesRawContract.php b/src/ServiceContracts/MessagesRawContract.php index 0afdc79..e27a6d7 100644 --- a/src/ServiceContracts/MessagesRawContract.php +++ b/src/ServiceContracts/MessagesRawContract.php @@ -6,10 +6,12 @@ use BeeperDesktop\Core\Contracts\BaseResponse; use BeeperDesktop\Core\Exceptions\APIException; +use BeeperDesktop\CursorNoLimit; use BeeperDesktop\CursorSearch; -use BeeperDesktop\CursorSortKey; use BeeperDesktop\Message; +use BeeperDesktop\Messages\MessageDeleteParams; use BeeperDesktop\Messages\MessageListParams; +use BeeperDesktop\Messages\MessageRetrieveParams; use BeeperDesktop\Messages\MessageSearchParams; use BeeperDesktop\Messages\MessageSendParams; use BeeperDesktop\Messages\MessageSendResponse; @@ -25,7 +27,24 @@ interface MessagesRawContract /** * @api * - * @param string $messageID Path param: ID of the message to edit + * @param string $messageID message ID + * @param array|MessageRetrieveParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function retrieve( + string $messageID, + array|MessageRetrieveParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse; + + /** + * @api + * + * @param string $messageID path param: Message ID * @param array|MessageUpdateParams $params * @param RequestOpts|null $requestOptions * @@ -42,11 +61,11 @@ public function update( /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array|MessageListParams $params * @param RequestOpts|null $requestOptions * - * @return BaseResponse> + * @return BaseResponse> * * @throws APIException */ @@ -56,6 +75,23 @@ public function list( RequestOptions|array|null $requestOptions = null, ): BaseResponse; + /** + * @api + * + * @param string $messageID path param: Message ID + * @param array|MessageDeleteParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function delete( + string $messageID, + array|MessageDeleteParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse; + /** * @api * @@ -74,7 +110,7 @@ public function search( /** * @api * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array|MessageSendParams $params * @param RequestOpts|null $requestOptions * diff --git a/src/Services/AccountsRawService.php b/src/Services/AccountsRawService.php index 64937d1..aa7a338 100644 --- a/src/Services/AccountsRawService.php +++ b/src/Services/AccountsRawService.php @@ -28,7 +28,7 @@ public function __construct(private Client $client) {} /** * @api * - * Lists chat accounts across networks (WhatsApp, Telegram, Twitter/X, etc.) actively connected to this Beeper Desktop instance + * List Chat Accounts connected to this Beeper Desktop instance, including bridge metadata and network identity. * * @param RequestOpts|null $requestOptions * diff --git a/src/Services/AccountsService.php b/src/Services/AccountsService.php index 313ef12..0a3f837 100644 --- a/src/Services/AccountsService.php +++ b/src/Services/AccountsService.php @@ -40,7 +40,7 @@ public function __construct(private Client $client) /** * @api * - * Lists chat accounts across networks (WhatsApp, Telegram, Twitter/X, etc.) actively connected to this Beeper Desktop instance + * List Chat Accounts connected to this Beeper Desktop instance, including bridge metadata and network identity. * * @param RequestOpts|null $requestOptions * diff --git a/src/Services/AssetsRawService.php b/src/Services/AssetsRawService.php index 051bf2a..a9ccc0a 100644 --- a/src/Services/AssetsRawService.php +++ b/src/Services/AssetsRawService.php @@ -14,6 +14,7 @@ use BeeperDesktop\Client; use BeeperDesktop\Core\Contracts\BaseResponse; use BeeperDesktop\Core\Exceptions\APIException; +use BeeperDesktop\Core\FileParam; use BeeperDesktop\RequestOptions; use BeeperDesktop\ServiceContracts\AssetsRawContract; @@ -33,7 +34,7 @@ public function __construct(private Client $client) {} /** * @api * - * Download a Matrix asset using its mxc:// or localmxc:// URL to the device running Beeper Desktop and return the local file URL. + * Download a Matrix file using its mxc:// or localmxc:// URL to the device running Beeper Desktop and return the local file URL. * * @param array{url: string}|AssetDownloadParams $params * @param RequestOpts|null $requestOptions @@ -69,7 +70,7 @@ public function download( * @param array{url: string}|AssetServeParams $params * @param RequestOpts|null $requestOptions * - * @return BaseResponse + * @return BaseResponse * * @throws APIException */ @@ -87,18 +88,19 @@ public function serve( method: 'get', path: 'v1/assets/serve', query: $parsed, + headers: ['Accept' => 'application/octet-stream'], options: $options, - convert: null, + convert: 'string', ); } /** * @api * - * Upload a file to a temporary location using multipart/form-data. Returns an uploadID that can be referenced when sending messages with attachments. + * Upload a file to a temporary location using multipart/form-data. Returns an uploadID that can be referenced when sending a message or materializing a draft attachment. * * @param array{ - * file: string, fileName?: string, mimeType?: string + * file: string|FileParam, fileName?: string, mimeType?: string * }|AssetUploadParams $params * @param RequestOpts|null $requestOptions * @@ -129,7 +131,7 @@ public function upload( /** * @api * - * Upload a file using a JSON body with base64-encoded content. Returns an uploadID that can be referenced when sending messages with attachments. Alternative to the multipart upload endpoint. + * Upload a file using a JSON body with base64-encoded content. Returns an uploadID that can be referenced when sending a message or materializing a draft attachment. Alternative to the multipart upload endpoint. * * @param array{ * content: string, fileName?: string, mimeType?: string diff --git a/src/Services/AssetsService.php b/src/Services/AssetsService.php index 6d77b41..0610d62 100644 --- a/src/Services/AssetsService.php +++ b/src/Services/AssetsService.php @@ -9,6 +9,7 @@ use BeeperDesktop\Assets\AssetUploadResponse; use BeeperDesktop\Client; use BeeperDesktop\Core\Exceptions\APIException; +use BeeperDesktop\Core\FileParam; use BeeperDesktop\Core\Util; use BeeperDesktop\RequestOptions; use BeeperDesktop\ServiceContracts\AssetsContract; @@ -36,9 +37,9 @@ public function __construct(private Client $client) /** * @api * - * Download a Matrix asset using its mxc:// or localmxc:// URL to the device running Beeper Desktop and return the local file URL. + * Download a Matrix file using its mxc:// or localmxc:// URL to the device running Beeper Desktop and return the local file URL. * - * @param string $url matrix content URL (mxc:// or localmxc://) for the asset to download + * @param string $url matrix content URL (mxc:// or localmxc://) for the file to download * @param RequestOpts|null $requestOptions * * @throws APIException @@ -60,7 +61,7 @@ public function download( * * Stream a file given an mxc://, localmxc://, or file:// URL. Downloads first if not cached. Supports Range requests for seeking in large files. * - * @param string $url Asset URL to serve. Accepts mxc://, localmxc://, or file:// URLs. + * @param string $url File URL to serve. Accepts mxc://, localmxc://, or file:// URLs. * @param RequestOpts|null $requestOptions * * @throws APIException @@ -68,7 +69,7 @@ public function download( public function serve( string $url, RequestOptions|array|null $requestOptions = null - ): mixed { + ): string { $params = Util::removeNulls(['url' => $url]); // @phpstan-ignore-next-line argument.type @@ -80,9 +81,9 @@ public function serve( /** * @api * - * Upload a file to a temporary location using multipart/form-data. Returns an uploadID that can be referenced when sending messages with attachments. + * Upload a file to a temporary location using multipart/form-data. Returns an uploadID that can be referenced when sending a message or materializing a draft attachment. * - * @param string $file the file to upload (max 500 MB) + * @param string|FileParam $file the file to upload (max 500 MB) * @param string $fileName Original filename. Defaults to the uploaded file name if omitted * @param string $mimeType MIME type. Auto-detected from magic bytes if omitted * @param RequestOpts|null $requestOptions @@ -90,7 +91,7 @@ public function serve( * @throws APIException */ public function upload( - string $file, + string|FileParam $file, ?string $fileName = null, ?string $mimeType = null, RequestOptions|array|null $requestOptions = null, @@ -108,7 +109,7 @@ public function upload( /** * @api * - * Upload a file using a JSON body with base64-encoded content. Returns an uploadID that can be referenced when sending messages with attachments. Alternative to the multipart upload endpoint. + * Upload a file using a JSON body with base64-encoded content. Returns an uploadID that can be referenced when sending a message or materializing a draft attachment. Alternative to the multipart upload endpoint. * * @param string $content Base64-encoded file content (max ~500MB decoded) * @param string $fileName Original filename. Generated if omitted diff --git a/src/Services/BeeperDesktopClientRawService.php b/src/Services/BeeperDesktopClientRawService.php index e40a60d..58f0394 100644 --- a/src/Services/BeeperDesktopClientRawService.php +++ b/src/Services/BeeperDesktopClientRawService.php @@ -15,6 +15,8 @@ use BeeperDesktop\ServiceContracts\BeeperDesktopClientRawContract; /** + * Top-level actions: focus the app window, jump to a chat, or run unified search across chats and messages. + * * @phpstan-import-type RequestOpts from \BeeperDesktop\RequestOptions */ final class BeeperDesktopClientRawService implements BeeperDesktopClientRawContract @@ -28,7 +30,7 @@ public function __construct(private Client $client) {} /** * @api * - * Focus Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill draft text and attachment. + * Focus Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill plain text and an image path. * * @param array{ * chatID?: string, diff --git a/src/Services/BeeperDesktopClientService.php b/src/Services/BeeperDesktopClientService.php index 9f2272f..8923440 100644 --- a/src/Services/BeeperDesktopClientService.php +++ b/src/Services/BeeperDesktopClientService.php @@ -13,6 +13,8 @@ use BeeperDesktop\ServiceContracts\BeeperDesktopClientContract; /** + * Top-level actions: focus the app window, jump to a chat, or run unified search across chats and messages. + * * @phpstan-import-type RequestOpts from \BeeperDesktop\RequestOptions */ final class BeeperDesktopClientService implements BeeperDesktopClientContract @@ -33,11 +35,11 @@ public function __construct(private Client $client) /** * @api * - * Focus Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill draft text and attachment. + * Focus Beeper Desktop and optionally navigate to a specific chat, message, or pre-fill plain text and an image path. * * @param string $chatID Optional Beeper chat ID (or local chat ID) to focus after opening the app. If omitted, only opens/focuses the app. - * @param string $draftAttachmentPath optional draft attachment path to populate in the message input field - * @param string $draftText optional draft text to populate in the message input field + * @param string $draftAttachmentPath optional image path to populate in the message input field + * @param string $draftText optional plain text to populate in the message input field * @param string $messageID Optional message ID. Jumps to that message in the chat when opening. * @param RequestOpts|null $requestOptions * diff --git a/src/Services/Chats/Messages/ReactionsRawService.php b/src/Services/Chats/Messages/ReactionsRawService.php index 80a8750..43fcf90 100644 --- a/src/Services/Chats/Messages/ReactionsRawService.php +++ b/src/Services/Chats/Messages/ReactionsRawService.php @@ -30,10 +30,10 @@ public function __construct(private Client $client) {} /** * @api * - * Remove the authenticated user's reaction from an existing message. + * Remove the reaction added by the authenticated user from an existing message. * - * @param string $messageID Path param: ID of the message to remove a reaction from - * @param array{chatID: string, reactionKey: string}|ReactionDeleteParams $params + * @param string $reactionKey Reaction key to remove (emoji, shortcode, or custom emoji key) + * @param array{chatID: string, messageID: string}|ReactionDeleteParams $params * @param RequestOpts|null $requestOptions * * @return BaseResponse @@ -41,7 +41,7 @@ public function __construct(private Client $client) {} * @throws APIException */ public function delete( - string $messageID, + string $reactionKey, array|ReactionDeleteParams $params, RequestOptions|array|null $requestOptions = null, ): BaseResponse { @@ -51,12 +51,18 @@ public function delete( ); $chatID = $parsed['chatID']; unset($parsed['chatID']); + $messageID = $parsed['messageID']; + unset($parsed['messageID']); // @phpstan-ignore-next-line return.type return $this->client->request( method: 'delete', - path: ['v1/chats/%1$s/messages/%2$s/reactions', $chatID, $messageID], - query: $parsed, + path: [ + 'v1/chats/%1$s/messages/%2$s/reactions/%3$s', + $chatID, + $messageID, + $reactionKey, + ], options: $options, convert: ReactionDeleteResponse::class, ); @@ -67,7 +73,7 @@ public function delete( * * Add a reaction to an existing message. * - * @param string $messageID Path param: ID of the message to add a reaction to + * @param string $messageID path param: Message ID * @param array{ * chatID: string, reactionKey: string, transactionID?: string * }|ReactionAddParams $params diff --git a/src/Services/Chats/Messages/ReactionsService.php b/src/Services/Chats/Messages/ReactionsService.php index 83d410b..7c8daa2 100644 --- a/src/Services/Chats/Messages/ReactionsService.php +++ b/src/Services/Chats/Messages/ReactionsService.php @@ -35,27 +35,27 @@ public function __construct(private Client $client) /** * @api * - * Remove the authenticated user's reaction from an existing message. + * Remove the reaction added by the authenticated user from an existing message. * - * @param string $messageID Path param: ID of the message to remove a reaction from - * @param string $chatID path param: Unique identifier of the chat - * @param string $reactionKey Query param: Reaction key to remove + * @param string $reactionKey Reaction key to remove (emoji, shortcode, or custom emoji key) + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param string $messageID message ID * @param RequestOpts|null $requestOptions * * @throws APIException */ public function delete( - string $messageID, - string $chatID, string $reactionKey, + string $chatID, + string $messageID, RequestOptions|array|null $requestOptions = null, ): ReactionDeleteResponse { $params = Util::removeNulls( - ['chatID' => $chatID, 'reactionKey' => $reactionKey] + ['chatID' => $chatID, 'messageID' => $messageID] ); // @phpstan-ignore-next-line argument.type - $response = $this->raw->delete($messageID, params: $params, requestOptions: $requestOptions); + $response = $this->raw->delete($reactionKey, params: $params, requestOptions: $requestOptions); return $response->parse(); } @@ -65,10 +65,10 @@ public function delete( * * Add a reaction to an existing message. * - * @param string $messageID Path param: ID of the message to add a reaction to - * @param string $chatID path param: Unique identifier of the chat + * @param string $messageID path param: Message ID + * @param string $chatID Path param: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param string $reactionKey Body param: Reaction key to add (emoji, shortcode, or custom emoji key) - * @param string $transactionID Body param: Optional transaction ID for deduplication and local echo tracking + * @param string $transactionID Body param: Optional transaction ID for deduplication and send tracking * @param RequestOpts|null $requestOptions * * @throws APIException diff --git a/src/Services/Chats/RemindersRawService.php b/src/Services/Chats/RemindersRawService.php index 319b3a8..2ad04e2 100644 --- a/src/Services/Chats/RemindersRawService.php +++ b/src/Services/Chats/RemindersRawService.php @@ -31,7 +31,7 @@ public function __construct(private Client $client) {} * * Set a reminder for a chat at a specific time * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array{reminder: Reminder|ReminderShape}|ReminderCreateParams $params * @param RequestOpts|null $requestOptions * @@ -64,7 +64,7 @@ public function create( * * Clear an existing reminder from a chat * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param RequestOpts|null $requestOptions * * @return BaseResponse diff --git a/src/Services/Chats/RemindersService.php b/src/Services/Chats/RemindersService.php index 8237a7e..dad2e0f 100644 --- a/src/Services/Chats/RemindersService.php +++ b/src/Services/Chats/RemindersService.php @@ -37,7 +37,7 @@ public function __construct(private Client $client) * * Set a reminder for a chat at a specific time * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param Reminder|ReminderShape $reminder Reminder configuration * @param RequestOpts|null $requestOptions * @@ -61,7 +61,7 @@ public function create( * * Clear an existing reminder from a chat * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param RequestOpts|null $requestOptions * * @throws APIException diff --git a/src/Services/ChatsRawService.php b/src/Services/ChatsRawService.php index 1df2645..4ad18ed 100644 --- a/src/Services/ChatsRawService.php +++ b/src/Services/ChatsRawService.php @@ -4,18 +4,25 @@ namespace BeeperDesktop\Services; +use BeeperDesktop\Chats\Chat; use BeeperDesktop\Chats\ChatArchiveParams; use BeeperDesktop\Chats\ChatCreateParams; -use BeeperDesktop\Chats\ChatCreateParams\Chat; +use BeeperDesktop\Chats\ChatCreateParams\Type; use BeeperDesktop\Chats\ChatListParams; use BeeperDesktop\Chats\ChatListParams\Direction; use BeeperDesktop\Chats\ChatListResponse; +use BeeperDesktop\Chats\ChatMarkReadParams; +use BeeperDesktop\Chats\ChatMarkUnreadParams; use BeeperDesktop\Chats\ChatNewResponse; use BeeperDesktop\Chats\ChatRetrieveParams; use BeeperDesktop\Chats\ChatSearchParams; use BeeperDesktop\Chats\ChatSearchParams\Inbox; use BeeperDesktop\Chats\ChatSearchParams\Scope; -use BeeperDesktop\Chats\ChatSearchParams\Type; +use BeeperDesktop\Chats\ChatStartParams; +use BeeperDesktop\Chats\ChatStartParams\User; +use BeeperDesktop\Chats\ChatStartResponse; +use BeeperDesktop\Chats\ChatUpdateParams; +use BeeperDesktop\Chats\ChatUpdateParams\Draft; use BeeperDesktop\Client; use BeeperDesktop\Core\Contracts\BaseResponse; use BeeperDesktop\Core\Exceptions\APIException; @@ -27,7 +34,8 @@ /** * Manage chats. * - * @phpstan-import-type ChatShape from \BeeperDesktop\Chats\ChatCreateParams\Chat + * @phpstan-import-type DraftShape from \BeeperDesktop\Chats\ChatUpdateParams\Draft + * @phpstan-import-type UserShape from \BeeperDesktop\Chats\ChatStartParams\User * @phpstan-import-type RequestOpts from \BeeperDesktop\RequestOptions */ final class ChatsRawService implements ChatsRawContract @@ -41,9 +49,15 @@ public function __construct(private Client $client) {} /** * @api * - * Create a single/group chat (mode='create') or start a direct chat from merged user data (mode='start'). + * Create a direct or group chat from participant IDs. Returns the created chat. * - * @param array{chat: Chat|ChatShape}|ChatCreateParams $params + * @param array{ + * accountID: string, + * participantIDs: list, + * type: Type|value-of, + * messageText?: string, + * title?: string, + * }|ChatCreateParams $params * @param RequestOpts|null $requestOptions * * @return BaseResponse @@ -63,7 +77,7 @@ public function create( return $this->client->request( method: 'post', path: 'v1/chats', - body: (object) $parsed['chat'], + body: (object) $parsed, options: $options, convert: ChatNewResponse::class, ); @@ -74,11 +88,11 @@ public function create( * * Retrieve chat details including metadata, participants, and latest message * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array{maxParticipantCount?: int|null}|ChatRetrieveParams $params * @param RequestOpts|null $requestOptions * - * @return BaseResponse<\BeeperDesktop\Chats\Chat> + * @return BaseResponse * * @throws APIException */ @@ -98,7 +112,50 @@ public function retrieve( path: ['v1/chats/%1$s', $chatID], query: $parsed, options: $options, - convert: \BeeperDesktop\Chats\Chat::class, + convert: Chat::class, + ); + } + + /** + * @api + * + * Update supported chat fields. Non-empty draft objects are accepted only when the current draft is empty. Send draft=null to clear the draft before setting new draft text or attachments. + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param array{ + * description?: string|null, + * draft?: Draft|DraftShape|null, + * imgURL?: string|null, + * isArchived?: bool, + * isLowPriority?: bool, + * isMuted?: bool, + * isPinned?: bool, + * messageExpirySeconds?: int|null, + * title?: string|null, + * }|ChatUpdateParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function update( + string $chatID, + array|ChatUpdateParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse { + [$parsed, $options] = ChatUpdateParams::parseRequest( + $params, + $requestOptions, + ); + + // @phpstan-ignore-next-line return.type + return $this->client->request( + method: 'patch', + path: ['v1/chats/%1$s', $chatID], + body: (object) $parsed, + options: $options, + convert: Chat::class, ); } @@ -143,7 +200,7 @@ public function list( * * Archive or unarchive a chat. Set archived=true to move to archive, archived=false to move back to inbox * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array{archived?: bool}|ChatArchiveParams $params * @param RequestOpts|null $requestOptions * @@ -174,7 +231,98 @@ public function archive( /** * @api * - * Search chats by title/network or participants using Beeper Desktop's renderer algorithm. + * Mark a chat as read, optionally through a specific message ID. + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param array{messageID?: string}|ChatMarkReadParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function markRead( + string $chatID, + array|ChatMarkReadParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse { + [$parsed, $options] = ChatMarkReadParams::parseRequest( + $params, + $requestOptions, + ); + + // @phpstan-ignore-next-line return.type + return $this->client->request( + method: 'post', + path: ['v1/chats/%1$s/read', $chatID], + body: (object) $parsed, + options: $options, + convert: Chat::class, + ); + } + + /** + * @api + * + * Mark a chat as unread, optionally from a specific message ID. + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param array{messageID?: string}|ChatMarkUnreadParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function markUnread( + string $chatID, + array|ChatMarkUnreadParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse { + [$parsed, $options] = ChatMarkUnreadParams::parseRequest( + $params, + $requestOptions, + ); + + // @phpstan-ignore-next-line return.type + return $this->client->request( + method: 'post', + path: ['v1/chats/%1$s/unread', $chatID], + body: (object) $parsed, + options: $options, + convert: Chat::class, + ); + } + + /** + * @api + * + * Force a delivery notification when supported by the underlying network. Currently intended for iMessage on macOS; unsupported networks return an error. + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function notifyAnyway( + string $chatID, + RequestOptions|array|null $requestOptions = null + ): BaseResponse { + // @phpstan-ignore-next-line return.type + return $this->client->request( + method: 'post', + path: ['v1/chats/%1$s/notify-anyway', $chatID], + options: $requestOptions, + convert: Chat::class, + ); + } + + /** + * @api + * + * Search chats by title, network, or participant names. * * @param array{ * accountIDs?: list, @@ -187,12 +335,12 @@ public function archive( * limit?: int, * query?: string, * scope?: Scope|value-of, - * type?: Type|value-of, + * type?: ChatSearchParams\Type|value-of, * unreadOnly?: bool|null, * }|ChatSearchParams $params * @param RequestOpts|null $requestOptions * - * @return BaseResponse> + * @return BaseResponse> * * @throws APIException */ @@ -211,8 +359,44 @@ public function search( path: 'v1/chats/search', query: $parsed, options: $options, - convert: \BeeperDesktop\Chats\Chat::class, + convert: Chat::class, page: CursorSearch::class, ); } + + /** + * @api + * + * Resolve a user/contact and open a direct chat. Reuses and returns an existing direct chat when one is found. Available in Beeper Desktop v4.2.808+. + * + * @param array{ + * accountID: string, + * user: User|UserShape, + * allowInvite?: bool, + * messageText?: string, + * }|ChatStartParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function start( + array|ChatStartParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse { + [$parsed, $options] = ChatStartParams::parseRequest( + $params, + $requestOptions, + ); + + // @phpstan-ignore-next-line return.type + return $this->client->request( + method: 'post', + path: 'v1/chats/start', + body: (object) $parsed, + options: $options, + convert: ChatStartResponse::class, + ); + } } diff --git a/src/Services/ChatsService.php b/src/Services/ChatsService.php index 8ca0439..6651871 100644 --- a/src/Services/ChatsService.php +++ b/src/Services/ChatsService.php @@ -4,13 +4,16 @@ namespace BeeperDesktop\Services; -use BeeperDesktop\Chats\ChatCreateParams\Chat; +use BeeperDesktop\Chats\Chat; +use BeeperDesktop\Chats\ChatCreateParams\Type; use BeeperDesktop\Chats\ChatListParams\Direction; use BeeperDesktop\Chats\ChatListResponse; use BeeperDesktop\Chats\ChatNewResponse; use BeeperDesktop\Chats\ChatSearchParams\Inbox; use BeeperDesktop\Chats\ChatSearchParams\Scope; -use BeeperDesktop\Chats\ChatSearchParams\Type; +use BeeperDesktop\Chats\ChatStartParams\User; +use BeeperDesktop\Chats\ChatStartResponse; +use BeeperDesktop\Chats\ChatUpdateParams\Draft; use BeeperDesktop\Client; use BeeperDesktop\Core\Exceptions\APIException; use BeeperDesktop\Core\Util; @@ -24,7 +27,8 @@ /** * Manage chats. * - * @phpstan-import-type ChatShape from \BeeperDesktop\Chats\ChatCreateParams\Chat + * @phpstan-import-type DraftShape from \BeeperDesktop\Chats\ChatUpdateParams\Draft + * @phpstan-import-type UserShape from \BeeperDesktop\Chats\ChatStartParams\User * @phpstan-import-type RequestOpts from \BeeperDesktop\RequestOptions */ final class ChatsService implements ChatsContract @@ -57,18 +61,34 @@ public function __construct(private Client $client) /** * @api * - * Create a single/group chat (mode='create') or start a direct chat from merged user data (mode='start'). + * Create a direct or group chat from participant IDs. Returns the created chat. * - * @param Chat|ChatShape $chat + * @param string $accountID account to create or start the chat on + * @param list $participantIDs user IDs to include in the new chat + * @param Type|value-of $type 'single' requires exactly one participantID; 'group' supports multiple participants and optional title + * @param string $messageText optional first message content if the platform requires it to create the chat + * @param string $title optional title for group chats; ignored for single chats on most networks * @param RequestOpts|null $requestOptions * * @throws APIException */ public function create( - Chat|array $chat, - RequestOptions|array|null $requestOptions = null + string $accountID, + array $participantIDs, + Type|string $type, + ?string $messageText = null, + ?string $title = null, + RequestOptions|array|null $requestOptions = null, ): ChatNewResponse { - $params = Util::removeNulls(['chat' => $chat]); + $params = Util::removeNulls( + [ + 'accountID' => $accountID, + 'participantIDs' => $participantIDs, + 'type' => $type, + 'messageText' => $messageText, + 'title' => $title, + ], + ); // @phpstan-ignore-next-line argument.type $response = $this->raw->create(params: $params, requestOptions: $requestOptions); @@ -81,17 +101,17 @@ public function create( * * Retrieve chat details including metadata, participants, and latest message * - * @param string $chatID unique identifier of the chat - * @param int|null $maxParticipantCount Maximum number of participants to return. Use -1 for all; otherwise 0–500. Defaults to all (-1). + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param int|null $maxParticipantCount Maximum number of participants to return. Use -1 for all; otherwise 0-500. Defaults to 100. List and search endpoints return up to 20 participants per chat. * @param RequestOpts|null $requestOptions * * @throws APIException */ public function retrieve( string $chatID, - ?int $maxParticipantCount = -1, + ?int $maxParticipantCount = 100, RequestOptions|array|null $requestOptions = null, - ): \BeeperDesktop\Chats\Chat { + ): Chat { $params = Util::removeNulls( ['maxParticipantCount' => $maxParticipantCount] ); @@ -102,6 +122,58 @@ public function retrieve( return $response->parse(); } + /** + * @api + * + * Update supported chat fields. Non-empty draft objects are accepted only when the current draft is empty. Send draft=null to clear the draft before setting new draft text or attachments. + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param string|null $description Group chat description/topic. Support depends on the chat account and chat permissions. + * @param Draft|DraftShape|null $draft Draft object to set or clear. Non-empty drafts are only accepted when the current draft is empty. Send draft=null to clear text and attachments together before setting a new draft. + * @param string|null $imgURL Local filesystem path to a group chat avatar image. Support depends on the chat account and chat permissions. + * @param bool $isArchived archive or unarchive the chat + * @param bool $isLowPriority mark or unmark the chat as low priority when supported by the account + * @param bool $isMuted mute or unmute the chat + * @param bool $isPinned pin or unpin the chat when supported by the account + * @param int|null $messageExpirySeconds disappearing-message timer in seconds, or null to clear when supported + * @param string|null $title Custom chat title. Support depends on the chat account and chat permissions. + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function update( + string $chatID, + ?string $description = null, + Draft|array|null $draft = null, + ?string $imgURL = null, + ?bool $isArchived = null, + ?bool $isLowPriority = null, + ?bool $isMuted = null, + ?bool $isPinned = null, + ?int $messageExpirySeconds = null, + ?string $title = null, + RequestOptions|array|null $requestOptions = null, + ): Chat { + $params = Util::removeNulls( + [ + 'description' => $description, + 'draft' => $draft, + 'imgURL' => $imgURL, + 'isArchived' => $isArchived, + 'isLowPriority' => $isLowPriority, + 'isMuted' => $isMuted, + 'isPinned' => $isPinned, + 'messageExpirySeconds' => $messageExpirySeconds, + 'title' => $title, + ], + ); + + // @phpstan-ignore-next-line argument.type + $response = $this->raw->update($chatID, params: $params, requestOptions: $requestOptions); + + return $response->parse(); + } + /** * @api * @@ -141,7 +213,7 @@ public function list( * * Archive or unarchive a chat. Set archived=true to move to archive, archived=false to move back to inbox * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param bool $archived True to archive, false to unarchive * @param RequestOpts|null $requestOptions * @@ -163,7 +235,75 @@ public function archive( /** * @api * - * Search chats by title/network or participants using Beeper Desktop's renderer algorithm. + * Mark a chat as read, optionally through a specific message ID. + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param string $messageID optional message ID to mark read through + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function markRead( + string $chatID, + ?string $messageID = null, + RequestOptions|array|null $requestOptions = null, + ): Chat { + $params = Util::removeNulls(['messageID' => $messageID]); + + // @phpstan-ignore-next-line argument.type + $response = $this->raw->markRead($chatID, params: $params, requestOptions: $requestOptions); + + return $response->parse(); + } + + /** + * @api + * + * Mark a chat as unread, optionally from a specific message ID. + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param string $messageID optional message ID to mark unread from + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function markUnread( + string $chatID, + ?string $messageID = null, + RequestOptions|array|null $requestOptions = null, + ): Chat { + $params = Util::removeNulls(['messageID' => $messageID]); + + // @phpstan-ignore-next-line argument.type + $response = $this->raw->markUnread($chatID, params: $params, requestOptions: $requestOptions); + + return $response->parse(); + } + + /** + * @api + * + * Force a delivery notification when supported by the underlying network. Currently intended for iMessage on macOS; unsupported networks return an error. + * + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function notifyAnyway( + string $chatID, + RequestOptions|array|null $requestOptions = null + ): Chat { + // @phpstan-ignore-next-line argument.type + $response = $this->raw->notifyAnyway($chatID, requestOptions: $requestOptions); + + return $response->parse(); + } + + /** + * @api + * + * Search chats by title, network, or participant names. * * @param list $accountIDs Provide an array of account IDs to filter chats from specific messaging accounts only * @param string $cursor Opaque pagination cursor; do not inspect. Use together with 'direction'. @@ -175,11 +315,11 @@ public function archive( * @param int $limit Set the maximum number of chats to retrieve. Valid range: 1-200, default is 50 * @param string $query Literal token search (non-semantic). Use single words users type (e.g., "dinner"). When multiple words provided, ALL must match. Case-insensitive. * @param Scope|value-of $scope search scope: 'titles' matches title + network; 'participants' matches participant names - * @param Type|value-of $type Specify the type of chats to retrieve: use "single" for direct messages, "group" for group chats, or "any" to get all types + * @param \BeeperDesktop\Chats\ChatSearchParams\Type|value-of<\BeeperDesktop\Chats\ChatSearchParams\Type> $type Specify the type of chats to retrieve: use "single" for direct messages, "group" for group chats, or "any" to get all types * @param bool|null $unreadOnly Set to true to only retrieve chats that have unread messages * @param RequestOpts|null $requestOptions * - * @return CursorSearch<\BeeperDesktop\Chats\Chat> + * @return CursorSearch * * @throws APIException */ @@ -194,7 +334,7 @@ public function search( int $limit = 50, ?string $query = null, Scope|string $scope = 'titles', - Type|string $type = 'any', + \BeeperDesktop\Chats\ChatSearchParams\Type|string $type = 'any', ?bool $unreadOnly = null, RequestOptions|array|null $requestOptions = null, ): CursorSearch { @@ -220,4 +360,39 @@ public function search( return $response->parse(); } + + /** + * @api + * + * Resolve a user/contact and open a direct chat. Reuses and returns an existing direct chat when one is found. Available in Beeper Desktop v4.2.808+. + * + * @param string $accountID account to create or start the chat on + * @param User|UserShape $user merged user-like contact payload used to resolve the best identifier + * @param bool $allowInvite whether invite-based DM creation is allowed when required by the platform + * @param string $messageText optional first message content if the platform requires it to create the chat + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function start( + string $accountID, + User|array $user, + bool $allowInvite = true, + ?string $messageText = null, + RequestOptions|array|null $requestOptions = null, + ): ChatStartResponse { + $params = Util::removeNulls( + [ + 'accountID' => $accountID, + 'user' => $user, + 'allowInvite' => $allowInvite, + 'messageText' => $messageText, + ], + ); + + // @phpstan-ignore-next-line argument.type + $response = $this->raw->start(params: $params, requestOptions: $requestOptions); + + return $response->parse(); + } } diff --git a/src/Services/InfoRawService.php b/src/Services/InfoRawService.php index 853a5b8..4b3fba5 100644 --- a/src/Services/InfoRawService.php +++ b/src/Services/InfoRawService.php @@ -12,6 +12,8 @@ use BeeperDesktop\ServiceContracts\InfoRawContract; /** + * Server discovery and capability metadata. Use /v1/info before authentication setup. + * * @phpstan-import-type RequestOpts from \BeeperDesktop\RequestOptions */ final class InfoRawService implements InfoRawContract @@ -25,7 +27,7 @@ public function __construct(private Client $client) {} /** * @api * - * Returns app, platform, server, and endpoint discovery metadata for this Beeper Desktop instance. + * Returns app, platform, server, endpoint discovery, OAuth, and WebSocket metadata for this Beeper Desktop instance. * * @param RequestOpts|null $requestOptions * @@ -42,6 +44,7 @@ public function retrieve( path: 'v1/info', options: $requestOptions, convert: InfoGetResponse::class, + security: [], ); } } diff --git a/src/Services/InfoService.php b/src/Services/InfoService.php index c38342e..adb1818 100644 --- a/src/Services/InfoService.php +++ b/src/Services/InfoService.php @@ -11,6 +11,8 @@ use BeeperDesktop\ServiceContracts\InfoContract; /** + * Server discovery and capability metadata. Use /v1/info before authentication setup. + * * @phpstan-import-type RequestOpts from \BeeperDesktop\RequestOptions */ final class InfoService implements InfoContract @@ -31,7 +33,7 @@ public function __construct(private Client $client) /** * @api * - * Returns app, platform, server, and endpoint discovery metadata for this Beeper Desktop instance. + * Returns app, platform, server, endpoint discovery, OAuth, and WebSocket metadata for this Beeper Desktop instance. * * @param RequestOpts|null $requestOptions * diff --git a/src/Services/MessagesRawService.php b/src/Services/MessagesRawService.php index 58ac32b..500e46f 100644 --- a/src/Services/MessagesRawService.php +++ b/src/Services/MessagesRawService.php @@ -7,15 +7,16 @@ use BeeperDesktop\Client; use BeeperDesktop\Core\Contracts\BaseResponse; use BeeperDesktop\Core\Exceptions\APIException; +use BeeperDesktop\CursorNoLimit; use BeeperDesktop\CursorSearch; -use BeeperDesktop\CursorSortKey; use BeeperDesktop\Message; +use BeeperDesktop\Messages\MessageDeleteParams; use BeeperDesktop\Messages\MessageListParams; use BeeperDesktop\Messages\MessageListParams\Direction; +use BeeperDesktop\Messages\MessageRetrieveParams; use BeeperDesktop\Messages\MessageSearchParams; use BeeperDesktop\Messages\MessageSearchParams\ChatType; use BeeperDesktop\Messages\MessageSearchParams\MediaType; -use BeeperDesktop\Messages\MessageSearchParams\Sender; use BeeperDesktop\Messages\MessageSendParams; use BeeperDesktop\Messages\MessageSendParams\Attachment; use BeeperDesktop\Messages\MessageSendResponse; @@ -38,12 +39,46 @@ final class MessagesRawService implements MessagesRawContract */ public function __construct(private Client $client) {} + /** + * @api + * + * Retrieve a message by final message ID, pendingMessageID, or Matrix event ID. Chat ID may be a Beeper chat ID or local chat ID. + * + * @param string $messageID message ID + * @param array{chatID: string}|MessageRetrieveParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function retrieve( + string $messageID, + array|MessageRetrieveParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse { + [$parsed, $options] = MessageRetrieveParams::parseRequest( + $params, + $requestOptions, + ); + $chatID = $parsed['chatID']; + unset($parsed['chatID']); + + // @phpstan-ignore-next-line return.type + return $this->client->request( + method: 'get', + path: ['v1/chats/%1$s/messages/%2$s', $chatID, $messageID], + options: $options, + convert: Message::class, + ); + } + /** * @api * * Edit the text content of an existing message. Messages with attachments cannot be edited. * - * @param string $messageID Path param: ID of the message to edit + * @param string $messageID path param: Message ID * @param array{chatID: string, text: string}|MessageUpdateParams $params * @param RequestOpts|null $requestOptions * @@ -78,13 +113,13 @@ public function update( * * List all messages in a chat with cursor-based pagination. Sorted by timestamp. * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array{ * cursor?: string, direction?: Direction|value-of * }|MessageListParams $params * @param RequestOpts|null $requestOptions * - * @return BaseResponse> + * @return BaseResponse> * * @throws APIException */ @@ -105,14 +140,51 @@ public function list( query: $parsed, options: $options, convert: Message::class, - page: CursorSortKey::class, + page: CursorNoLimit::class, + ); + } + + /** + * @api + * + * Delete a message by final message ID. Pending message IDs are not accepted because messages cannot be deleted while sending. + * + * @param string $messageID path param: Message ID + * @param array{ + * chatID: string, forEveryone?: bool|null + * }|MessageDeleteParams $params + * @param RequestOpts|null $requestOptions + * + * @return BaseResponse + * + * @throws APIException + */ + public function delete( + string $messageID, + array|MessageDeleteParams $params, + RequestOptions|array|null $requestOptions = null, + ): BaseResponse { + [$parsed, $options] = MessageDeleteParams::parseRequest( + $params, + $requestOptions, + ); + $chatID = $parsed['chatID']; + unset($parsed['chatID']); + + // @phpstan-ignore-next-line return.type + return $this->client->request( + method: 'delete', + path: ['v1/chats/%1$s/messages/%2$s', $chatID, $messageID], + query: $parsed, + options: $options, + convert: null, ); } /** * @api * - * Search messages across chats using Beeper's message index + * Search messages across chats. * * @param array{ * accountIDs?: list, @@ -127,7 +199,7 @@ public function list( * limit?: int, * mediaTypes?: list>, * query?: string, - * sender?: string|Sender|value-of, + * sender?: string, * }|MessageSearchParams $params * @param RequestOpts|null $requestOptions * @@ -160,7 +232,7 @@ public function search( * * Send a text message to a specific chat. Supports replying to existing messages. Returns a pending message ID. * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param array{ * attachment?: Attachment|AttachmentShape, * replyToMessageID?: string, diff --git a/src/Services/MessagesService.php b/src/Services/MessagesService.php index 70f0386..dbb0afd 100644 --- a/src/Services/MessagesService.php +++ b/src/Services/MessagesService.php @@ -7,13 +7,12 @@ use BeeperDesktop\Client; use BeeperDesktop\Core\Exceptions\APIException; use BeeperDesktop\Core\Util; +use BeeperDesktop\CursorNoLimit; use BeeperDesktop\CursorSearch; -use BeeperDesktop\CursorSortKey; use BeeperDesktop\Message; use BeeperDesktop\Messages\MessageListParams\Direction; use BeeperDesktop\Messages\MessageSearchParams\ChatType; use BeeperDesktop\Messages\MessageSearchParams\MediaType; -use BeeperDesktop\Messages\MessageSearchParams\Sender; use BeeperDesktop\Messages\MessageSendParams\Attachment; use BeeperDesktop\Messages\MessageSendResponse; use BeeperDesktop\Messages\MessageUpdateResponse; @@ -41,13 +40,37 @@ public function __construct(private Client $client) $this->raw = new MessagesRawService($client); } + /** + * @api + * + * Retrieve a message by final message ID, pendingMessageID, or Matrix event ID. Chat ID may be a Beeper chat ID or local chat ID. + * + * @param string $messageID message ID + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function retrieve( + string $messageID, + string $chatID, + RequestOptions|array|null $requestOptions = null, + ): Message { + $params = Util::removeNulls(['chatID' => $chatID]); + + // @phpstan-ignore-next-line argument.type + $response = $this->raw->retrieve($messageID, params: $params, requestOptions: $requestOptions); + + return $response->parse(); + } + /** * @api * * Edit the text content of an existing message. Messages with attachments cannot be edited. * - * @param string $messageID Path param: ID of the message to edit - * @param string $chatID path param: Unique identifier of the chat + * @param string $messageID path param: Message ID + * @param string $chatID Path param: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param string $text Body param: New text content for the message * @param RequestOpts|null $requestOptions * @@ -72,12 +95,12 @@ public function update( * * List all messages in a chat with cursor-based pagination. Sorted by timestamp. * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param string $cursor Opaque pagination cursor; do not inspect. Use together with 'direction'. * @param Direction|value-of $direction Pagination direction used with 'cursor': 'before' fetches older results, 'after' fetches newer results. Defaults to 'before' when only 'cursor' is provided. * @param RequestOpts|null $requestOptions * - * @return CursorSortKey + * @return CursorNoLimit * * @throws APIException */ @@ -86,7 +109,7 @@ public function list( ?string $cursor = null, Direction|string|null $direction = null, RequestOptions|array|null $requestOptions = null, - ): CursorSortKey { + ): CursorNoLimit { $params = Util::removeNulls( ['cursor' => $cursor, 'direction' => $direction] ); @@ -100,7 +123,35 @@ public function list( /** * @api * - * Search messages across chats using Beeper's message index + * Delete a message by final message ID. Pending message IDs are not accepted because messages cannot be deleted while sending. + * + * @param string $messageID path param: Message ID + * @param string $chatID Path param: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. + * @param bool|null $forEveryone query param: True to request deletion for everyone when the network supports it; false to delete only for the authenticated user when supported + * @param RequestOpts|null $requestOptions + * + * @throws APIException + */ + public function delete( + string $messageID, + string $chatID, + ?bool $forEveryone = true, + RequestOptions|array|null $requestOptions = null, + ): mixed { + $params = Util::removeNulls( + ['chatID' => $chatID, 'forEveryone' => $forEveryone] + ); + + // @phpstan-ignore-next-line argument.type + $response = $this->raw->delete($messageID, params: $params, requestOptions: $requestOptions); + + return $response->parse(); + } + + /** + * @api + * + * Search messages across chats. * * @param list $accountIDs limit search to specific account IDs * @param list $chatIDs limit search to specific chat IDs @@ -114,7 +165,7 @@ public function list( * @param int $limit maximum number of messages to return * @param list> $mediaTypes Filter messages by media types. Use ['any'] for any media type, or specify exact types like ['video', 'image']. Omit for no media filtering. * @param string $query Literal word search (non-semantic). Finds messages containing these EXACT words in any order. Use single words users actually type, not concepts or phrases. Example: use "dinner" not "dinner plans", use "sick" not "health issues". If omitted, returns results filtered only by other parameters. - * @param string|Sender|value-of $sender Filter by sender: 'me' (messages sent by the authenticated user), 'others' (messages sent by others), or a specific user ID string (user.id). + * @param string $sender Filter by sender: 'me' (messages sent by the authenticated user), 'others' (messages sent by others), or a specific user ID string (user.id). * @param RequestOpts|null $requestOptions * * @return CursorSearch @@ -134,7 +185,7 @@ public function search( int $limit = 20, ?array $mediaTypes = null, ?string $query = null, - Sender|string|null $sender = null, + ?string $sender = null, RequestOptions|array|null $requestOptions = null, ): CursorSearch { $params = Util::removeNulls( @@ -166,10 +217,10 @@ public function search( * * Send a text message to a specific chat. Supports replying to existing messages. Returns a pending message ID. * - * @param string $chatID unique identifier of the chat + * @param string $chatID Chat ID. Input routes also accept the local chat ID from this Beeper Desktop installation when available. * @param Attachment|AttachmentShape $attachment Single attachment to send with the message * @param string $replyToMessageID Provide a message ID to send this as a reply to an existing message - * @param string $text Text content of the message you want to send. You may use markdown. + * @param string $text Draft text. Plain text and Markdown are converted to Matrix HTML with the same rules used by send and edit. * @param RequestOpts|null $requestOptions * * @throws APIException diff --git a/src/User.php b/src/User.php index d45604c..9787d40 100644 --- a/src/User.php +++ b/src/User.php @@ -53,7 +53,7 @@ final class User implements BaseModel public ?string $fullName; /** - * Avatar image URL if available. May be temporary or local-only to this device; download promptly if durable access is needed. + * Avatar image URL if available. This may be a remote URL, Matrix media URL, data URL, or local filesystem URL depending on source and endpoint. May be temporary or local-only to this device; download promptly if durable access is needed. */ #[Optional] public ?string $imgURL; @@ -170,7 +170,7 @@ public function withFullName(string $fullName): self } /** - * Avatar image URL if available. May be temporary or local-only to this device; download promptly if durable access is needed. + * Avatar image URL if available. This may be a remote URL, Matrix media URL, data URL, or local filesystem URL depending on source and endpoint. May be temporary or local-only to this device; download promptly if durable access is needed. */ public function withImgURL(string $imgURL): self { diff --git a/src/Version.php b/src/Version.php index fa38b72..16c7e53 100644 --- a/src/Version.php +++ b/src/Version.php @@ -5,5 +5,5 @@ namespace BeeperDesktop; // x-release-please-start-version -const VERSION = '0.0.1'; +const VERSION = '5.0.0'; // x-release-please-end diff --git a/tests/Services/AssetsTest.php b/tests/Services/AssetsTest.php index 4ae3576..6aaeb61 100644 --- a/tests/Services/AssetsTest.php +++ b/tests/Services/AssetsTest.php @@ -6,6 +6,7 @@ use BeeperDesktop\Assets\AssetUploadBase64Response; use BeeperDesktop\Assets\AssetUploadResponse; use BeeperDesktop\Client; +use BeeperDesktop\Core\FileParam; use BeeperDesktop\Core\Util; use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\Test; @@ -57,7 +58,7 @@ public function testServe(): void $result = $this->client->assets->serve(url: 'x'); // @phpstan-ignore-next-line method.alreadyNarrowedType - $this->assertNull($result); + $this->assertIsString($result); } #[Test] @@ -66,13 +67,15 @@ public function testServeWithOptionalParams(): void $result = $this->client->assets->serve(url: 'x'); // @phpstan-ignore-next-line method.alreadyNarrowedType - $this->assertNull($result); + $this->assertIsString($result); } #[Test] public function testUpload(): void { - $result = $this->client->assets->upload(file: 'file'); + $result = $this->client->assets->upload( + file: FileParam::fromString('Example data', filename: uniqid('file-upload-', true)), + ); // @phpstan-ignore-next-line method.alreadyNarrowedType $this->assertInstanceOf(AssetUploadResponse::class, $result); @@ -82,9 +85,9 @@ public function testUpload(): void public function testUploadWithOptionalParams(): void { $result = $this->client->assets->upload( - file: 'file', + file: FileParam::fromString('Example data', filename: uniqid('file-upload-', true)), fileName: 'fileName', - mimeType: 'mimeType' + mimeType: 'mimeType', ); // @phpstan-ignore-next-line method.alreadyNarrowedType diff --git a/tests/Services/Chats/Messages/ReactionsTest.php b/tests/Services/Chats/Messages/ReactionsTest.php index de3125a..08f4115 100644 --- a/tests/Services/Chats/Messages/ReactionsTest.php +++ b/tests/Services/Chats/Messages/ReactionsTest.php @@ -32,9 +32,9 @@ protected function setUp(): void public function testDelete(): void { $result = $this->client->chats->messages->reactions->delete( - 'messageID', + 'x', chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', - reactionKey: 'x' + messageID: '1343993' ); // @phpstan-ignore-next-line method.alreadyNarrowedType @@ -45,9 +45,9 @@ public function testDelete(): void public function testDeleteWithOptionalParams(): void { $result = $this->client->chats->messages->reactions->delete( - 'messageID', + 'x', chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', - reactionKey: 'x' + messageID: '1343993' ); // @phpstan-ignore-next-line method.alreadyNarrowedType @@ -58,7 +58,7 @@ public function testDeleteWithOptionalParams(): void public function testAdd(): void { $result = $this->client->chats->messages->reactions->add( - 'messageID', + '1343993', chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', reactionKey: 'x' ); @@ -71,7 +71,7 @@ public function testAdd(): void public function testAddWithOptionalParams(): void { $result = $this->client->chats->messages->reactions->add( - 'messageID', + '1343993', chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', reactionKey: 'x', transactionID: 'transactionID', diff --git a/tests/Services/Chats/RemindersTest.php b/tests/Services/Chats/RemindersTest.php index e489678..12e0ca9 100644 --- a/tests/Services/Chats/RemindersTest.php +++ b/tests/Services/Chats/RemindersTest.php @@ -31,7 +31,9 @@ public function testCreate(): void { $result = $this->client->chats->reminders->create( '!NCdzlIaMjZUmvmvyHU:beeper.com', - reminder: ['remindAtMs' => 0] + reminder: [ + 'remindAt' => new \DateTimeImmutable('2025-08-31T23:30:12.520Z'), + ], ); // @phpstan-ignore-next-line method.alreadyNarrowedType @@ -43,7 +45,10 @@ public function testCreateWithOptionalParams(): void { $result = $this->client->chats->reminders->create( '!NCdzlIaMjZUmvmvyHU:beeper.com', - reminder: ['remindAtMs' => 0, 'dismissOnIncomingMessage' => true], + reminder: [ + 'remindAt' => new \DateTimeImmutable('2025-08-31T23:30:12.520Z'), + 'dismissOnIncomingMessage' => true, + ], ); // @phpstan-ignore-next-line method.alreadyNarrowedType diff --git a/tests/Services/ChatsTest.php b/tests/Services/ChatsTest.php index 71d8111..57b0f7d 100644 --- a/tests/Services/ChatsTest.php +++ b/tests/Services/ChatsTest.php @@ -5,6 +5,7 @@ use BeeperDesktop\Chats\Chat; use BeeperDesktop\Chats\ChatListResponse; use BeeperDesktop\Chats\ChatNewResponse; +use BeeperDesktop\Chats\ChatStartResponse; use BeeperDesktop\Client; use BeeperDesktop\Core\Util; use BeeperDesktop\CursorNoLimit; @@ -34,7 +35,11 @@ protected function setUp(): void #[Test] public function testCreate(): void { - $result = $this->client->chats->create(chat: ['accountID' => 'accountID']); + $result = $this->client->chats->create( + accountID: 'accountID', + participantIDs: ['string'], + type: 'single' + ); // @phpstan-ignore-next-line method.alreadyNarrowedType $this->assertInstanceOf(ChatNewResponse::class, $result); @@ -44,22 +49,11 @@ public function testCreate(): void public function testCreateWithOptionalParams(): void { $result = $this->client->chats->create( - chat: [ - 'accountID' => 'accountID', - 'allowInvite' => true, - 'messageText' => 'messageText', - 'mode' => 'create', - 'participantIDs' => ['string'], - 'title' => 'title', - 'type' => 'single', - 'user' => [ - 'id' => 'id', - 'email' => 'email', - 'fullName' => 'fullName', - 'phoneNumber' => 'phoneNumber', - 'username' => 'username', - ], - ], + accountID: 'accountID', + participantIDs: ['string'], + type: 'single', + messageText: 'messageText', + title: 'title', ); // @phpstan-ignore-next-line method.alreadyNarrowedType @@ -75,6 +69,15 @@ public function testRetrieve(): void $this->assertInstanceOf(Chat::class, $result); } + #[Test] + public function testUpdate(): void + { + $result = $this->client->chats->update('!NCdzlIaMjZUmvmvyHU:beeper.com'); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(Chat::class, $result); + } + #[Test] public function testList(): void { @@ -98,6 +101,37 @@ public function testArchive(): void $this->assertNull($result); } + #[Test] + public function testMarkRead(): void + { + $result = $this->client->chats->markRead('!NCdzlIaMjZUmvmvyHU:beeper.com'); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(Chat::class, $result); + } + + #[Test] + public function testMarkUnread(): void + { + $result = $this->client->chats->markUnread( + '!NCdzlIaMjZUmvmvyHU:beeper.com' + ); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(Chat::class, $result); + } + + #[Test] + public function testNotifyAnyway(): void + { + $result = $this->client->chats->notifyAnyway( + '!NCdzlIaMjZUmvmvyHU:beeper.com' + ); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(Chat::class, $result); + } + #[Test] public function testSearch(): void { @@ -111,4 +145,33 @@ public function testSearch(): void $this->assertInstanceOf(Chat::class, $item); } } + + #[Test] + public function testStart(): void + { + $result = $this->client->chats->start(accountID: 'accountID', user: []); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(ChatStartResponse::class, $result); + } + + #[Test] + public function testStartWithOptionalParams(): void + { + $result = $this->client->chats->start( + accountID: 'accountID', + user: [ + 'id' => 'id', + 'email' => 'email', + 'fullName' => 'fullName', + 'phoneNumber' => 'phoneNumber', + 'username' => 'username', + ], + allowInvite: true, + messageText: 'messageText', + ); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(ChatStartResponse::class, $result); + } } diff --git a/tests/Services/MessagesTest.php b/tests/Services/MessagesTest.php index 0d1cab8..67ee229 100644 --- a/tests/Services/MessagesTest.php +++ b/tests/Services/MessagesTest.php @@ -4,8 +4,8 @@ use BeeperDesktop\Client; use BeeperDesktop\Core\Util; +use BeeperDesktop\CursorNoLimit; use BeeperDesktop\CursorSearch; -use BeeperDesktop\CursorSortKey; use BeeperDesktop\Message; use BeeperDesktop\Messages\MessageSendResponse; use BeeperDesktop\Messages\MessageUpdateResponse; @@ -31,11 +31,35 @@ protected function setUp(): void $this->client = $client; } + #[Test] + public function testRetrieve(): void + { + $result = $this->client->messages->retrieve( + '1343993', + chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com' + ); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(Message::class, $result); + } + + #[Test] + public function testRetrieveWithOptionalParams(): void + { + $result = $this->client->messages->retrieve( + '1343993', + chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com' + ); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertInstanceOf(Message::class, $result); + } + #[Test] public function testUpdate(): void { $result = $this->client->messages->update( - 'messageID', + '1343993', chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', text: 'x' ); @@ -48,7 +72,7 @@ public function testUpdate(): void public function testUpdateWithOptionalParams(): void { $result = $this->client->messages->update( - 'messageID', + '1343993', chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', text: 'x' ); @@ -63,7 +87,7 @@ public function testList(): void $page = $this->client->messages->list('!NCdzlIaMjZUmvmvyHU:beeper.com'); // @phpstan-ignore-next-line method.alreadyNarrowedType - $this->assertInstanceOf(CursorSortKey::class, $page); + $this->assertInstanceOf(CursorNoLimit::class, $page); if ($item = $page->getItems()[0] ?? null) { // @phpstan-ignore-next-line method.alreadyNarrowedType @@ -71,6 +95,31 @@ public function testList(): void } } + #[Test] + public function testDelete(): void + { + $result = $this->client->messages->delete( + '1343993', + chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com' + ); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertNull($result); + } + + #[Test] + public function testDeleteWithOptionalParams(): void + { + $result = $this->client->messages->delete( + '1343993', + chatID: '!NCdzlIaMjZUmvmvyHU:beeper.com', + forEveryone: true + ); + + // @phpstan-ignore-next-line method.alreadyNarrowedType + $this->assertNull($result); + } + #[Test] public function testSearch(): void {