From 4365fb71d8535e9532608a7605e0bfbd96ade35a Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 10 May 2024 14:08:16 +0200 Subject: [PATCH] First commit --- .github/FUNDING.yml | 1 + .github/workflows/jekyll-gh-pages.yml | 51 +++++++ .github/workflows/main.yml | 68 +++++++++ .gitignore | 11 ++ .php-cs-fixer.dist.php | 21 +++ LICENSE | 203 +++++++++++++++++++++++++ NOTICE | 5 + README.md | 87 +++++++++++ composer.json | 38 +++++ docs/danog/DialogId/DialogId.md | 200 +++++++++++++++++++++++++ docs/index.md | 19 +++ examples/1-type.php | 59 ++++++++ infection.json5 | 22 +++ phpunit.xml | 18 +++ psalm.xml | 17 +++ src/DialogId.php | 206 ++++++++++++++++++++++++++ test/DialogIdTest.php | 99 +++++++++++++ 17 files changed, 1125 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/jekyll-gh-pages.yml create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 composer.json create mode 100644 docs/danog/DialogId/DialogId.md create mode 100644 docs/index.md create mode 100644 examples/1-type.php create mode 100644 infection.json5 create mode 100644 phpunit.xml create mode 100644 psalm.xml create mode 100644 src/DialogId.php create mode 100644 test/DialogIdTest.php diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..72fc5ff --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: danog diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml new file mode 100644 index 0000000..0ebd768 --- /dev/null +++ b/.github/workflows/jekyll-gh-pages.yml @@ -0,0 +1,51 @@ +# Sample workflow for building and deploying a Jekyll site to GitHub Pages +name: Deploy Jekyll with GitHub Pages dependencies preinstalled + +on: + # Runs on pushes targeting the default branch + push: + branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Pages + uses: actions/configure-pages@v5 + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: ./ + destination: ./_site + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..d426e7f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,68 @@ +name: build +on: + pull_request: + push: +jobs: + run: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + php-versions: ["8.1", "8.2", "8.3"] + name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, intl, sockets + coverage: xdebug + + - name: Check environment + run: | + php --version + composer --version + + - name: Get composer cache directory + id: composercache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composercache.outputs.dir }} + key: ${{ matrix.os }}-composer-${{ matrix.php-versions }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ matrix.os }}-composer-${{ matrix.php-versions }}- + + - name: Install dependencies + run: | + composer install --prefer-dist + + - name: Run codestyle check + env: + PHP_CS_FIXER_IGNORE_ENV: 1 + run: | + vendor/bin/php-cs-fixer --diff --dry-run -v fix + + - name: Run unit tests + run: | + vendor/bin/phpunit --coverage-text --coverage-clover build/logs/clover.xml + + - name: Run mutation tests + env: + STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + run: | + vendor/bin/infection --show-mutations + + - name: Run Psalm analysis + run: | + vendor/bin/psalm --shepherd + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: danog/tgbotdialog-id diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50f923c --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +infection/ +coverage +*.mp4 +.vscode +build +composer.lock +vendor +.php_cs.cache +coverage +.phpunit* +*.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..c694527 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,21 @@ + true, + ]); + } +}; + +$config->getFinder() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/examples') + ->in(__DIR__ . '/test'); + +$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__; + +$config->setCacheFile($cacheDir . '/.php_cs.cache'); + +return $config; diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6b0b127 --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..b260eff --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +tg-dialog-id - A library to work with Telegram bot API dialog IDs + +Copyright 2024 Daniil Gentili + +Homepage: https://github.com/danog/tg-dialog-id diff --git a/README.md b/README.md new file mode 100644 index 0000000..38bc3a0 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# tg-dialog-id + +[![codecov](https://codecov.io/gh/danog/tg-dialog-id/branch/master/graph/badge.svg)](https://codecov.io/gh/danog/tg-dialog-id) +[![Psalm coverage](https://shepherd.dev/github/danog/tg-dialog-id/coverage.svg)](https://shepherd.dev/github/danog/tg-dialog-id) +[![Psalm level 1](https://shepherd.dev/github/danog/tg-dialog-id/level.svg)](https://shepherd.dev/github/danog/tg-dialog-id) +[![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fdanog%2Ftg-dialog-id%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/danog/tg-dialog-id/master) +![License](https://img.shields.io/github/license/danog/tg-dialog-id) + +A library to work with Telegram bot API dialog IDs. + +Created by Daniil Gentili (https://daniil.it). + +This library was initially created for [MadelineProto](https://docs.madelineproto.xyz), an async PHP client API for the telegram MTProto protocol. + +## Installation + +```bash +composer require danog/tg-dialog-id +``` + +## Usage + +```php + bot API dialog ID +expect(DialogId::fromSupergroupOrChannelId(1234567890) === -1001234567890); + +// Converts an MTProto chat ID => bot API dialog ID +expect(DialogId::fromChatId(123456789) === -123456789); + +// Converts an MTProto user ID => bot API dialog ID +expect(DialogId::fromUserId(123456789) === 123456789); + +// Converts an MTProto secret chat ID => bot API dialog ID +expect(DialogId::fromSecretChatId(123456789) === -1999876543211); + +// Converts a bot API dialog ID => MTProto supergroup/channel ID +expect(DialogId::toSupergroupOrChannelId(-1001234567890) === 1234567890); + +// Converts a bot API dialog ID => MTProto chat ID +expect(DialogId::toChatId(-123456789) === 123456789); + +// Converts a bot API dialog ID => MTProto user ID +expect(DialogId::toUserId(123456789) === 123456789); + +// Converts a bot API dialog ID => MTProto secret chat ID +expect(DialogId::toSecretChatId(-1999876543211) === 123456789); + +expect(DialogId::getType(101374607) === DialogId::USER); +expect(DialogId::getType(-123456789) === DialogId::CHAT); +expect(DialogId::getType(-1001234567890) === DialogId::CHANNEL_OR_SUPERGROUP); +expect(DialogId::getType(-1999898625393) === DialogId::SECRET_CHAT); + +expect(DialogId::isUser(1099511627775) === true); +expect(DialogId::isChat(-999_999_999_999) === true); +expect(DialogId::isSupergroupOrChannel(-1997852516352) === true); +expect(DialogId::isSecretChat(-2002147483648) === true); + +expect(DialogId::isUser(101374607) === true); +expect(DialogId::isChat(-101374607) === true); +expect(DialogId::isSupergroupOrChannel(-1001234567890) === true); +expect(DialogId::isSecretChat(-1999898625393) === true); + +echo "OK!".PHP_EOL; +``` + +## API Documentation + +Click [here »](https://github.com/danog/tg-dialog-id/blob/master/docs/index.md) to view the API documentation. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..fe83726 --- /dev/null +++ b/composer.json @@ -0,0 +1,38 @@ +{ + "name": "danog/tg-dialog-id", + "description": "A library to work with Telegram bot API dialog IDs", + "type": "library", + "license": "Apache-2.0", + "autoload": { + "psr-4": { + "danog\\DialogId\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "danog\\TestDialogId\\": "test/" + } + }, + "authors": [ + { + "name": "Daniil Gentili", + "email": "daniil@daniil.it" + } + ], + "require": { + "php-64bit": ">=8.1" + }, + "require-dev": { + "vimeo/psalm": "dev-master", + "phpunit/phpunit": "^11.0.9|^10", + "amphp/php-cs-fixer-config": "^2.0.1", + "infection/infection": "^0.28.1", + "danog/phpdoc": "^0.1.22", + "amphp/file": "^3.1" + }, + "config": { + "allow-plugins": { + "infection/extension-installer": true + } + } +} diff --git a/docs/danog/DialogId/DialogId.md b/docs/danog/DialogId/DialogId.md new file mode 100644 index 0000000..72b79a5 --- /dev/null +++ b/docs/danog/DialogId/DialogId.md @@ -0,0 +1,200 @@ +--- +title: "danog\\DialogId\\DialogId: Represents the type of a bot API dialog ID." +description: "" + +--- +# `danog\DialogId\DialogId` +[Back to index](../../index.md) + +> Author: Daniil Gentili + + +Represents the type of a bot API dialog ID. + + + + +## Constants +* `danog\DialogId\DialogId::USER`: Dialog type: user. + +* `danog\DialogId\DialogId::CHAT`: Dialog type: chat. + +* `danog\DialogId\DialogId::CHANNEL_OR_SUPERGROUP`: Dialog type: supergroup or channel, see https://core.telegram.org/api/channel for more info. + +* `danog\DialogId\DialogId::SECRET_CHAT`: Dialog type: secret chat. + +## Properties +* `$name`: `string` + +## Method list: +* [`getType(integer $id): self`](#getType) +* [`isSupergroupOrChannel(int $id): bool`](#isSupergroupOrChannel) +* [`isChat(int $id): bool`](#isChat) +* [`isUser(int $id): bool`](#isUser) +* [`isSecretChat(int $id): bool`](#isSecretChat) +* [`fromSecretChatId(int $id): int`](#fromSecretChatId) +* [`toSecretChatId(int $id): int`](#toSecretChatId) +* [`fromSupergroupOrChannelId(int $id): int`](#fromSupergroupOrChannelId) +* [`toSupergroupOrChannelId(int $id): int`](#toSupergroupOrChannelId) +* [`fromChatId(int $id): int`](#fromChatId) +* [`toChatId(int $id): int`](#toChatId) +* [`fromUserId(int $id): int`](#fromUserId) +* [`toUserId(int $id): int`](#toUserId) +* [`cases(): array`](#cases) + +## Methods: +### `getType(integer $id): self` + +Get the type of a dialog using just its bot API dialog ID. + + +Parameters: + +* `$id`: `integer` Bot API ID. + + + +### `isSupergroupOrChannel(int $id): bool` + +Checks whether the provided bot API ID is a supergroup or channel ID. + + +Parameters: + +* `$id`: `int` + + + +### `isChat(int $id): bool` + +Checks whether the provided bot API ID is a chat ID. + + +Parameters: + +* `$id`: `int` + + + +### `isUser(int $id): bool` + +Checks whether the provided bot API ID is a user ID. + + +Parameters: + +* `$id`: `int` + + + +### `isSecretChat(int $id): bool` + +Checks whether the provided bot API ID is a secret chat ID. + + +Parameters: + +* `$id`: `int` + + + +### `fromSecretChatId(int $id): int` + +Convert MTProto secret chat ID to bot API secret chat ID. + + +Parameters: + +* `$id`: `int` MTProto secret chat ID + + +Return value: Bot API secret chat ID + + +### `toSecretChatId(int $id): int` + +Convert bot API secret chat ID to MTProto secret chat ID. + + +Parameters: + +* `$id`: `int` Bot API secret chat ID + + +Return value: MTProto secret chat ID + + +### `fromSupergroupOrChannelId(int $id): int` + +Convert MTProto channel ID to bot API channel ID. + + +Parameters: + +* `$id`: `int` MTProto channel ID + + + +### `toSupergroupOrChannelId(int $id): int` + +Convert bot API channel ID to MTProto channel ID. + + +Parameters: + +* `$id`: `int` Bot API channel ID + + + +### `fromChatId(int $id): int` + +Convert MTProto chat ID to bot API chat ID. + + +Parameters: + +* `$id`: `int` MTProto chat ID + + + +### `toChatId(int $id): int` + +Convert bot API chat ID to MTProto chat ID. + + +Parameters: + +* `$id`: `int` Bot API chat ID + + + +### `fromUserId(int $id): int` + +Convert MTProto user ID to bot API user ID. + + +Parameters: + +* `$id`: `int` MTProto user ID + + + +### `toUserId(int $id): int` + +Convert bot API user ID to MTProto user ID. + + +Parameters: + +* `$id`: `int` Bot API user ID + + + +### `cases(): array` + + + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..029baca --- /dev/null +++ b/docs/index.md @@ -0,0 +1,19 @@ +--- +description: "A library to work with Telegram bot API dialog IDs" +title: "danog/tgbot-dialog-id" + +--- +# `danog/tgbot-dialog-id` + +A library to work with Telegram bot API dialog IDs + + + + +## Classes +* [\danog\DialogId\DialogId: Represents the type of a bot API dialog ID.](danog/DialogId/DialogId.md) + + + +--- +Generated by [danog/phpdoc](https://phpdoc.daniil.it). \ No newline at end of file diff --git a/examples/1-type.php b/examples/1-type.php new file mode 100644 index 0000000..21f344c --- /dev/null +++ b/examples/1-type.php @@ -0,0 +1,59 @@ + bot API dialog ID +expect(DialogId::fromSupergroupOrChannelId(1234567890) === -1001234567890); + +// Converts an MTProto chat ID => bot API dialog ID +expect(DialogId::fromChatId(123456789) === -123456789); + +// Converts an MTProto user ID => bot API dialog ID +expect(DialogId::fromUserId(123456789) === 123456789); + +// Converts an MTProto secret chat ID => bot API dialog ID +expect(DialogId::fromSecretChatId(123456789) === -1999876543211); + +// Converts a bot API dialog ID => MTProto supergroup/channel ID +expect(DialogId::toSupergroupOrChannelId(-1001234567890) === 1234567890); + +// Converts a bot API dialog ID => MTProto chat ID +expect(DialogId::toChatId(-123456789) === 123456789); + +// Converts a bot API dialog ID => MTProto user ID +expect(DialogId::toUserId(123456789) === 123456789); + +// Converts a bot API dialog ID => MTProto secret chat ID +expect(DialogId::toSecretChatId(-1999876543211) === 123456789); + +expect(DialogId::getType(101374607) === DialogId::USER); +expect(DialogId::getType(-123456789) === DialogId::CHAT); +expect(DialogId::getType(-1001234567890) === DialogId::CHANNEL_OR_SUPERGROUP); +expect(DialogId::getType(-1999898625393) === DialogId::SECRET_CHAT); + +expect(DialogId::isUser(1099511627775) === true); +expect(DialogId::isChat(-999_999_999_999) === true); +expect(DialogId::isSupergroupOrChannel(-1997852516352) === true); +expect(DialogId::isSecretChat(-2002147483648) === true); + +expect(DialogId::isUser(101374607) === true); +expect(DialogId::isChat(-101374607) === true); +expect(DialogId::isSupergroupOrChannel(-1001234567890) === true); +expect(DialogId::isSecretChat(-1999898625393) === true); + +echo "OK!".PHP_EOL; diff --git a/infection.json5 b/infection.json5 new file mode 100644 index 0000000..72dd197 --- /dev/null +++ b/infection.json5 @@ -0,0 +1,22 @@ +{ + "$schema": "vendor/infection/infection/resources/schema.json", + "source": { + "directories": [ + "src" + ] + }, + "timeout": 10, + "logs": { + "text": "infection/infection.log", + "html": "infection/infection.html", + "summary": "infection/summary.log", + "perMutator": "infection/per-mutator.md", + "stryker": { + "badge": "master" + } + }, + "tmpDir": ".infection-cache", + "mutators": { + "@default": true + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..3eb8bd6 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + test + + + + + src + + + \ No newline at end of file diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..8e05c23 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/DialogId.php b/src/DialogId.php new file mode 100644 index 0000000..9795ae3 --- /dev/null +++ b/src/DialogId.php @@ -0,0 +1,206 @@ +name); + } + return $id; + } + /** + * Convert bot API secret chat ID to MTProto secret chat ID. + * + * @param int $id Bot API secret chat ID + * + * @return int MTProto secret chat ID + */ + public static function toSecretChatId(int $id): int + { + $type = self::getType($id); + if ($type !== self::SECRET_CHAT) { + throw new AssertionError("Expected a secret chat ID, got the following type: ".$type->name); + } + return $id - self::ZERO_SECRET_CHAT_ID; + } + + /** + * Convert MTProto channel ID to bot API channel ID. + * + * @param int $id MTProto channel ID + */ + public static function fromSupergroupOrChannelId(int $id): int + { + $id = self::ZERO_CHANNEL_ID - $id; + $type = self::getType($id); + if ($type !== self::CHANNEL_OR_SUPERGROUP) { + throw new AssertionError("Expected a supergroup/channel ID, but produced the following type: ".$type->name); + } + return $id; + } + /** + * Convert bot API channel ID to MTProto channel ID. + * + * @param int $id Bot API channel ID + */ + public static function toSupergroupOrChannelId(int $id): int + { + $type = self::getType($id); + if ($type !== self::CHANNEL_OR_SUPERGROUP) { + throw new AssertionError("Expected a supergroup/channel ID, got the following type: ".$type->name); + } + return (-$id) + self::ZERO_CHANNEL_ID; + } + + /** + * Convert MTProto chat ID to bot API chat ID. + * + * @param int $id MTProto chat ID + */ + public static function fromChatId(int $id): int + { + $id = -$id; + $type = self::getType($id); + if ($type !== self::CHAT) { + throw new AssertionError("Expected a chat ID, but produced the following type: ".$type->name); + } + return $id; + } + /** + * Convert bot API chat ID to MTProto chat ID. + * + * @param int $id Bot API chat ID + */ + public static function toChatId(int $id): int + { + $type = self::getType($id); + if ($type !== self::CHAT) { + throw new AssertionError("Expected a chat ID, got the following type: ".$type->name); + } + return -$id; + } + + /** + * Convert MTProto user ID to bot API user ID. + * + * @param int $id MTProto user ID + */ + public static function fromUserId(int $id): int + { + $type = self::getType($id); + if ($type !== self::USER) { + throw new AssertionError("Expected a user ID, but produced the following type: ".$type->name); + } + return $id; + } + /** + * Convert bot API user ID to MTProto user ID. + * + * @param int $id Bot API user ID + */ + public static function toUserId(int $id): int + { + $type = self::getType($id); + if ($type !== self::USER) { + throw new AssertionError("Expected a user ID, got the following type: ".$type->name); + } + return $id; + } +} diff --git a/test/DialogIdTest.php b/test/DialogIdTest.php new file mode 100644 index 0000000..443bb17 --- /dev/null +++ b/test/DialogIdTest.php @@ -0,0 +1,99 @@ +assertSame(DialogId::USER, DialogId::getType(101374607)); + $this->assertSame(DialogId::CHAT, DialogId::getType(-101374607)); + $this->assertSame(DialogId::CHANNEL_OR_SUPERGROUP, DialogId::getType(-1001234567890)); + $this->assertSame(DialogId::SECRET_CHAT, DialogId::getType(-1999898625393)); + + $this->assertTrue(DialogId::isUser(1099511627775)); + $this->assertTrue(DialogId::isChat(-999_999_999_999)); + $this->assertTrue(DialogId::isSupergroupOrChannel(-1997852516352)); + $this->assertTrue(DialogId::isSecretChat(-2002147483648)); + + $this->assertTrue(DialogId::isUser(101374607)); + $this->assertTrue(DialogId::isChat(-101374607)); + $this->assertTrue(DialogId::isSupergroupOrChannel(-1001234567890)); + $this->assertTrue(DialogId::isSecretChat(-1999898625393)); + + $this->assertSame(101374607, DialogId::toUserId(101374607)); + $this->assertSame(101374607, DialogId::toChatId(-101374607)); + $this->assertSame(1234567890, DialogId::toSupergroupOrChannelId(-1001234567890)); + $this->assertSame(101374607, DialogId::toSecretChatId(-1999898625393)); + + $this->assertSame(101374607, DialogId::fromUserId(101374607)); + $this->assertSame(-101374607, DialogId::fromChatId(101374607)); + $this->assertSame(-1001234567890, DialogId::fromSupergroupOrChannelId(1234567890)); + $this->assertSame(-1999898625393, DialogId::fromSecretChatId(101374607)); + } + + public function testException1(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Invalid ID -2999898625393 provided!"); + $this->assertTrue(DialogId::isSecretChat(-2999898625393)); + } + public function testException2(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Invalid ID 0 provided!"); + $this->assertTrue(DialogId::getType(0)); + } + public function testException3(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Expected a chat ID, but produced the following type: USER"); + DialogId::fromChatId(-100); + } + public function testException4(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Expected a user ID, but produced the following type: CHAT"); + DialogId::fromUserId(-100); + } + public function testException5(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Expected a supergroup/channel ID, but produced the following type: CHAT"); + DialogId::fromSupergroupOrChannelId(-100); + } + public function testException6(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Expected a secret chat ID, but produced the following type: CHAT"); + DialogId::fromSecretChatId((1 << 40) - 1); + } + public function testException3_rev(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Expected a chat ID, got the following type: USER"); + DialogId::toChatId(100); + } + public function testException4_rev(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Expected a user ID, got the following type: CHAT"); + DialogId::toUserId(-100); + } + public function testException5_rev(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Expected a supergroup/channel ID, got the following type: CHAT"); + DialogId::toSupergroupOrChannelId(-100); + } + public function testException6_rev(): void + { + $this->expectException(AssertionError::class); + $this->expectExceptionMessage("Expected a secret chat ID, got the following type: USER"); + DialogId::toSecretChatId((1 << 40) - 1); + } +}