├── .gitattributes ├── .gitignore ├── .phpstorm.meta.php ├── src ├── Exception │ ├── RuntimeException.php │ ├── TokenInvalidException.php │ └── InvalidArgumentException.php ├── ProviderInterface.php ├── AccessTokenInterface.php ├── AccessToken │ ├── AppAccessToken.php │ ├── TenantAccessToken.php │ ├── AccessTokenProvider.php │ └── AccessToken.php ├── Http │ ├── ClientProvider.php │ └── Client.php ├── Oauth │ ├── OauthProvider.php │ └── Oauth.php ├── Contact │ ├── ContactProvider.php │ └── Contact.php ├── Message │ ├── MessageProvider.php │ └── Message.php ├── Robot │ ├── RobotProvider.php │ └── Robot.php ├── ConfigProvider.php ├── HasAccessToken.php ├── Config │ └── Config.php ├── Application.php └── Factory.php ├── phpunit.xml ├── publish └── feishu.php ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .travis.yml ├── phpstan.neon ├── README.md ├── composer.json └── .php-cs-fixer.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /tests export-ignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | *.cache 4 | *.log 5 | .env 6 | -------------------------------------------------------------------------------- /.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | ./tests/ 14 | 15 | -------------------------------------------------------------------------------- /publish/feishu.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'base_uri' => 'https://open.feishu.cn', 15 | 'http_errors' => false, 16 | 'timeout' => 2, 17 | ], 18 | 'applications' => [ 19 | 'default' => [ 20 | 'app_id' => env('FEISHU_APPID', ''), 21 | 'app_secret' => env('FEISHU_SECRET', ''), 22 | ], 23 | ], 24 | ]; 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Release 8 | 9 | jobs: 10 | release: 11 | name: Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | - name: Create Release 17 | id: create_release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | tag_name: ${{ github.ref }} 23 | release_name: Release ${{ github.ref }} 24 | draft: false 25 | prerelease: false 26 | -------------------------------------------------------------------------------- /src/Http/ClientProvider.php: -------------------------------------------------------------------------------- 1 | new Client($pimple, $config->getHttp()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Oauth/OauthProvider.php: -------------------------------------------------------------------------------- 1 | new Oauth( 24 | $pimple[Client::getName()], 25 | $pimple[AppAccessToken::getName()] 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Contact/ContactProvider.php: -------------------------------------------------------------------------------- 1 | new Contact( 24 | $pimple[Client::getName()], 25 | $pimple[TenantAccessToken::getName()] 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Message/MessageProvider.php: -------------------------------------------------------------------------------- 1 | new Message( 24 | $pimple[Client::getName()], 25 | $pimple[TenantAccessToken::getName()] 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Robot/RobotProvider.php: -------------------------------------------------------------------------------- 1 | new Robot( 25 | $pimple[Client::getName()], 26 | $pimple[TenantAccessToken::getName()], 27 | $pimple[Message::getName()] 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AccessToken/AccessTokenProvider.php: -------------------------------------------------------------------------------- 1 | new AppAccessToken( 24 | $pimple[Config::getName()], 25 | $pimple[Client::getName()] 26 | ); 27 | 28 | $pimple[TenantAccessToken::getName()] = fn () => new TenantAccessToken( 29 | $pimple[Config::getName()], 30 | $pimple[Client::getName()] 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'scan' => [ 21 | 'paths' => [ 22 | __DIR__, 23 | ], 24 | ], 25 | ], 26 | 'publish' => [ 27 | [ 28 | 'id' => 'config', 29 | 'description' => 'The config for feishu sdk.', 30 | 'source' => __DIR__ . '/../publish/feishu.php', 31 | 'destination' => BASE_PATH . '/config/autoload/feishu.php', 32 | ], 33 | ], 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: required 4 | 5 | matrix: 6 | include: 7 | - php: 7.2 8 | env: SW_VERSION="4.5.2" 9 | - php: 7.3 10 | env: SW_VERSION="4.5.2" 11 | - php: 7.4 12 | env: SW_VERSION="4.5.2" 13 | - php: master 14 | env: SW_VERSION="4.5.2" 15 | 16 | allow_failures: 17 | - php: master 18 | 19 | services: 20 | - mysql 21 | - redis-server 22 | - docker 23 | 24 | before_install: 25 | - export PHP_MAJOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 1)" 26 | - export PHP_MINOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 2)" 27 | - echo $PHP_MAJOR 28 | - echo $PHP_MINOR 29 | 30 | install: 31 | - cd $TRAVIS_BUILD_DIR 32 | - bash ./tests/swoole.install.sh 33 | - phpenv config-rm xdebug.ini || echo "xdebug not available" 34 | - phpenv config-add ./tests/ci.ini 35 | 36 | before_script: 37 | - cd $TRAVIS_BUILD_DIR 38 | - composer config -g process-timeout 900 && composer update 39 | 40 | script: 41 | - composer analyse 42 | - composer test 43 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | # Magic behaviour with __get, __set, __call and __callStatic is not exactly static analyser-friendly :) 2 | # Fortunately, You can ingore it by the following config. 3 | # 4 | # vendor/bin/phpstan analyse app --memory-limit 200M -l 0 5 | # 6 | includes: 7 | # 需要执行 composer require phpstan/phpstan-deprecation-rules --dev 8 | - vendor/phpstan/phpstan-deprecation-rules/rules.neon 9 | 10 | parameters: 11 | reportUnmatchedIgnoredErrors: false 12 | ignoreErrors: 13 | - '#Static call to instance method Hyperf\\HttpServer\\Router\\Router::[a-zA-Z0-9\\_]+\(\)#' 14 | - '#Static call to instance method Hyperf\\DbConnection\\Db::[a-zA-Z0-9\\_]+\(\)#' 15 | 16 | services: 17 | # 检测使用 match 匹配 enum 时,枚举必须全部覆盖 18 | - class: PHPStan\Rules\Comparison\MatchExpressionRule 19 | arguments: 20 | checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison% 21 | disableUnreachable: %featureToggles.disableUnreachableBranchesRules% 22 | reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% 23 | treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% 24 | 25 | tags: 26 | - phpstan.rules.rule 27 | -------------------------------------------------------------------------------- /src/HasAccessToken.php: -------------------------------------------------------------------------------- 1 | client->client($this->token); 22 | 23 | $response = $client->request($method, $uri, $options); 24 | 25 | return $this->client->handleResponse($response); 26 | } 27 | 28 | public function request(string $method, string $uri, array $options = []): array 29 | { 30 | try { 31 | return $this->__request($method, $uri, $options); 32 | } catch (TokenInvalidException) { 33 | $this->token->getToken(true); 34 | return $this->__request($method, $uri, $options); 35 | } 36 | } 37 | 38 | public function handleResponse(ResponseInterface $response): array 39 | { 40 | return $this->client->handleResponse($response); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 飞书SDK 2 | 3 | [![PHPUnit](https://github.com/limingxinleo/feishu-sdk/actions/workflows/test.yml/badge.svg)](https://github.com/limingxinleo/feishu-sdk/actions/workflows/test.yml) 4 | 5 | ``` 6 | composer require limingxinleo/feishu 7 | ``` 8 | 9 | ## 使用 10 | 11 | 具体使用方法请查看 [飞书文档](https://open.feishu.cn/document/home/index) 12 | 13 | ### 快速开始 14 | 15 | `Hyperf` 框架中,可以直接使用 `Fan\Feishu\Factory`。 16 | 17 | 1. 发布配置 18 | 19 | ```shell 20 | php bin/hyperf.php vendor:publish limingxinleo/feishu 21 | ``` 22 | 23 | 2. 注入并使用 24 | 25 | ```php 26 | factory->get('default')->contact->batchGetUserId(emails: ['l@hyperf.io']); 39 | } 40 | } 41 | ``` 42 | 43 | 其他框架,可以自行 `new Application()` 使用。 44 | 45 | ```php 46 | 'xxx', 52 | 'app_secret' => 'xxx', 53 | 'http' => [ 54 | 'base_uri' => 'https://open.feishu.cn', 55 | 'http_errors' => false, 56 | 'timeout' => 2, 57 | ], 58 | ]); 59 | 60 | $result = $app->contact->batchGetUserId(emails: ['l@hyperf.io']); 61 | ``` 62 | -------------------------------------------------------------------------------- /src/Config/Config.php: -------------------------------------------------------------------------------- 1 | '', 22 | * 'app_secret' => '', 23 | * 'http' => [ 24 | * 'base_uri' => 'https://open.feishu.cn/', 25 | * 'timeout' => 2, 26 | * 'http_errors' => false, 27 | * ], 28 | * ] 29 | */ 30 | public function __construct(protected array $config) 31 | { 32 | } 33 | 34 | public function getAppId(): string 35 | { 36 | return $this->config['app_id']; 37 | } 38 | 39 | public function getAppSecret(): string 40 | { 41 | return $this->config['app_secret']; 42 | } 43 | 44 | #[ArrayShape(['base_uri' => 'string', 'timeout' => 'int'])] 45 | public function getHttp(): array 46 | { 47 | return $this->config['http']; 48 | } 49 | 50 | public function toArray(): array 51 | { 52 | return $this->config; 53 | } 54 | 55 | public static function getName(): string 56 | { 57 | return 'config'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "limingxinleo/feishu", 3 | "type": "library", 4 | "license": "MIT", 5 | "keywords": [ 6 | "php", 7 | "hyperf" 8 | ], 9 | "description": "飞书SDK", 10 | "autoload": { 11 | "psr-4": { 12 | "Fan\\Feishu\\": "src/" 13 | } 14 | }, 15 | "autoload-dev": { 16 | "psr-4": { 17 | "HyperfTest\\": "tests" 18 | } 19 | }, 20 | "require": { 21 | "php": ">=8.0", 22 | "hyperf/codec": "^3.0", 23 | "hyperf/guzzle": "^3.0", 24 | "hyperf/support": "^3.0", 25 | "jetbrains/phpstorm-attributes": "^1.0", 26 | "pimple/pimple": "^3.5" 27 | }, 28 | "require-dev": { 29 | "friendsofphp/php-cs-fixer": "^3.0", 30 | "hyperf/config": "^3.0", 31 | "hyperf/di": "^3.0", 32 | "hyperf/testing": "^3.0", 33 | "mockery/mockery": "^1.3", 34 | "phpstan/phpstan": "^1.0", 35 | "phpstan/phpstan-deprecation-rules": "^1.1" 36 | }, 37 | "minimum-stability": "dev", 38 | "prefer-stable": true, 39 | "config": { 40 | "optimize-autoloader": true, 41 | "sort-packages": true 42 | }, 43 | "scripts": { 44 | "test": "phpunit -c phpunit.xml --colors=always", 45 | "analyse": "phpstan analyse --memory-limit 300M -l 0 ./src", 46 | "cs-fix": "php-cs-fixer fix $1" 47 | }, 48 | "extra": { 49 | "hyperf": { 50 | "config": "Fan\\Feishu\\ConfigProvider" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Oauth/Oauth.php: -------------------------------------------------------------------------------- 1 | $this->token->getId(), 35 | 'redirect_uri' => $uri, 36 | 'state' => $state, 37 | ]); 38 | } 39 | 40 | public function getUserInfo(string $code): array 41 | { 42 | $ret = $this->request('POST', 'open-apis/authen/v1/access_token', [ 43 | RequestOptions::JSON => [ 44 | 'grant_type' => 'authorization_code', 45 | 'code' => $code, 46 | ], 47 | ]); 48 | 49 | return $ret['data'] ?? []; 50 | } 51 | 52 | public static function getName(): string 53 | { 54 | return 'oauth'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/AccessToken/AccessToken.php: -------------------------------------------------------------------------------- 1 | isExpired() && ! $refresh) { 33 | return $this->token; 34 | } 35 | 36 | $response = $this->client->client()->request('POST', 'open-apis/auth/v3/' . static::getName() . '/internal/', [ 37 | RequestOptions::JSON => [ 38 | 'app_id' => $this->config->getAppId(), 39 | 'app_secret' => $this->config->getAppSecret(), 40 | ], 41 | ]); 42 | 43 | $ret = $this->client->handleResponse($response); 44 | 45 | $this->expireTime = $ret['expire'] + time(); 46 | 47 | return $this->token = $ret[static::getName()]; 48 | } 49 | 50 | public function getId(): string 51 | { 52 | return $this->config->getAppId(); 53 | } 54 | 55 | protected function isExpired(): bool 56 | { 57 | return $this->expireTime <= time() + 60; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Http/Client.php: -------------------------------------------------------------------------------- 1 | config; 35 | if ($token) { 36 | $config[RequestOptions::HEADERS]['Authorization'] = 'Bearer ' . $token->getToken(); 37 | } 38 | 39 | return make(GuzzleHttp\Client::class, [$config]); 40 | } 41 | 42 | public function handleResponse(ResponseInterface $response): array 43 | { 44 | $ret = Json::decode((string) $response->getBody()); 45 | if ($ret['code'] !== 0) { 46 | $code = (int) $ret['code']; 47 | if ($code >= 99991661 && $code <= 99991668) { 48 | throw new TokenInvalidException(); 49 | } 50 | 51 | throw new RuntimeException($ret['msg'] ?? 'http request failed.', $ret['code']); 52 | } 53 | 54 | return $ret; 55 | } 56 | 57 | public static function getName(): string 58 | { 59 | return 'http'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | '', 41 | * 'app_secret' => '', 42 | * 'http' => [ 43 | * 'base_uri' => 'https://open.feishu.cn/', 44 | * 'timeout' => 2, 45 | * 'http_errors' => false, 46 | * ], 47 | * ] 48 | */ 49 | public function __construct(array $config) 50 | { 51 | $config = new Config\Config($config); 52 | $this->container = new Container([ 53 | 'config' => $config, 54 | ]); 55 | 56 | foreach ($this->providers as $provider) { 57 | $this->container->register(new $provider()); 58 | } 59 | } 60 | 61 | public function __get(string $name) 62 | { 63 | return $this->container[$name] ?? null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | config = $this->formatConfig($config); 29 | } 30 | 31 | public function get(string $name): Application 32 | { 33 | $app = $this->applications[$name] ?? null; 34 | if ($app instanceof Application) { 35 | return $app; 36 | } 37 | 38 | return $this->applications[$name] = $this->make($name); 39 | } 40 | 41 | public function make(string $name): Application 42 | { 43 | $config = $this->config['applications'][$name] ?? null; 44 | if (empty($config)) { 45 | throw new InvalidArgumentException(sprintf('Config %s is invalid.', $name)); 46 | } 47 | 48 | $http = $this->config['http'] ?? []; 49 | 50 | return new Application(array_replace_recursive(['http' => $http], $config)); 51 | } 52 | 53 | /** 54 | * @return [ 55 | * 'http' => [ 56 | * 'base_uri' => '', 57 | * 'timeout' => 2, 58 | * ], 59 | * 'applications' => [ 60 | * 'default' => [ 61 | * 'app_id' => '', 62 | * 'app_secret' => '', 63 | * 'http' => [ 64 | * 'base_uri' => '', 65 | * 'timeout' => 2, 66 | * ], 67 | * ], 68 | * ], 69 | * ] 70 | */ 71 | private function formatConfig(ConfigInterface $config): array 72 | { 73 | return $config->get('feishu', [ 74 | 'http' => [ 75 | 'base_uri' => 'https://open.feishu.cn', 76 | 'timeout' => 2, 77 | ], 78 | 'applications' => [], 79 | ]); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | SWOOLE_VERSION: 'v5.0.2' 7 | SWOW_VERSION: 'develop' 8 | 9 | jobs: 10 | ci: 11 | name: Test PHP ${{ matrix.php-version }} on ${{ matrix.engine }} 12 | runs-on: "${{ matrix.os }}" 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | php-version: ['8.0'] 17 | engine: ['none', 'swoole', 'swow'] 18 | max-parallel: 5 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | - name: Setup PHP 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php-version }} 26 | tools: phpize 27 | ini-values: opcache.enable_cli=0 28 | coverage: none 29 | - name: Setup Swoole 30 | if: ${{ matrix.engine == 'swoole' }} 31 | run: | 32 | sudo apt-get update 33 | sudo apt-get install libcurl4-openssl-dev 34 | wget https://github.com/swoole/swoole-src/archive/${SWOOLE_VERSION}.tar.gz -O swoole.tar.gz 35 | mkdir -p swoole 36 | tar -xf swoole.tar.gz -C swoole --strip-components=1 37 | rm swoole.tar.gz 38 | cd swoole 39 | phpize 40 | ./configure --enable-openssl --enable-http2 --enable-swoole-curl --enable-swoole-json 41 | make -j$(nproc) 42 | sudo make install 43 | sudo sh -c "echo extension=swoole > /etc/php/${{ matrix.php-version }}/cli/conf.d/swoole.ini" 44 | sudo sh -c "echo swoole.use_shortname='Off' >> /etc/php/${{ matrix.php-version }}/cli/conf.d/swoole.ini" 45 | php --ri swoole 46 | - name: Setup Swow 47 | if: ${{ matrix.engine == 'swow' }} 48 | run: | 49 | wget https://github.com/swow/swow/archive/"${SWOW_VERSION}".tar.gz -O swow.tar.gz 50 | mkdir -p swow 51 | tar -xf swow.tar.gz -C swow --strip-components=1 52 | rm swow.tar.gz 53 | cd swow/ext || exit 54 | 55 | phpize 56 | ./configure --enable-debug 57 | make -j "$(nproc)" 58 | sudo make install 59 | sudo sh -c "echo extension=swow > /etc/php/${{ matrix.php-version }}/cli/conf.d/swow.ini" 60 | - name: Setup Packages 61 | run: composer update -o --no-scripts 62 | - name: Run Test Cases 63 | run: | 64 | composer analyse 65 | composer test 66 | -------------------------------------------------------------------------------- /src/Robot/Robot.php: -------------------------------------------------------------------------------- 1 | request('GET', 'open-apis/bot/v3/info/'); 37 | 38 | return $ret['bot']; 39 | } 40 | 41 | /** 42 | * 群组列表. 43 | */ 44 | public function groupList(int $pageSize = 10, string $pageToken = null) 45 | { 46 | $query = ['page_size' => $pageSize]; 47 | if ($pageToken) { 48 | $query['page_token'] = $pageToken; 49 | } 50 | 51 | $ret = $this->request('GET', 'open-apis/chat/v4/list', [ 52 | RequestOptions::QUERY => $query, 53 | ]); 54 | 55 | return $ret['data'] ?? []; 56 | } 57 | 58 | public function getOpenId(): string 59 | { 60 | if (empty($this->openId)) { 61 | $info = $this->info(); 62 | $this->openId = $info['open_id']; 63 | } 64 | 65 | return $this->openId; 66 | } 67 | 68 | /** 69 | * @param $data = [ 70 | * 'receive_id' => 'oc_5ad11d72b830411d72b836c20', 71 | * 'msg_type' => 'text', 72 | * 'content' => [ 73 | * 'text' => '', 74 | * ], 75 | * ] 76 | */ 77 | public function send(array $data, string $type = 'chat_id') 78 | { 79 | return $this->message->send($data, $type); 80 | } 81 | 82 | /** 83 | * @deprecated 84 | */ 85 | public function sendText(string $chatId, string $text) 86 | { 87 | return $this->message->sendText( 88 | [ 89 | 'chat_id' => $chatId, 90 | ], 91 | $text, 92 | ); 93 | } 94 | 95 | public static function getName(): string 96 | { 97 | return 'robot'; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Message/Message.php: -------------------------------------------------------------------------------- 1 | 'oc_5ad11d72b830411d72b836c20', 34 | * 'msg_type' => 'text', 35 | * 'content' => [ 36 | * 'text' => '', 37 | * ], 38 | * ] 39 | */ 40 | public function send(array $data, string $type = 'chat_id') 41 | { 42 | $data['content'] = Json::encode($data['content']); 43 | $ret = $this->request('POST', 'open-apis/im/v1/messages', [ 44 | RequestOptions::JSON => $data, 45 | RequestOptions::QUERY => [ 46 | 'receive_id_type' => $type, 47 | ], 48 | ]); 49 | 50 | return $ret['data'] ?? []; 51 | } 52 | 53 | /** 54 | * @deprecated 55 | * @see https://open.feishu.cn/document/ukTMukTMukTM/uUjNz4SN2MjL1YzM 56 | * @param $data = [ 57 | * 'open_id' => 'ou_5ad573a6411d72b8305fda3a9c15c70e', 58 | * 'chat_id' => 'oc_5ad11d72b830411d72b836c20', 59 | * 'user_id' => '92e39a99', 60 | * 'email' => 'fanlv@gmail.com', 61 | * ] 62 | */ 63 | public function sendText(array $data, string $text) 64 | { 65 | $data['msg_type'] = 'text'; 66 | $data['content']['text'] = $text; 67 | 68 | $type = null; 69 | foreach (['chat_id', 'open_id', 'user_id', 'email'] as $key) { 70 | if (isset($data[$key])) { 71 | $type = $key; 72 | $data['receive_id'] = $data[$key]; 73 | unset($data[$key]); 74 | break; 75 | } 76 | } 77 | 78 | if (! isset($type)) { 79 | throw new InvalidArgumentException("Couldn't guess the type for message request."); 80 | } 81 | 82 | return $this->send($data, $type); 83 | } 84 | 85 | public static function getName(): string 86 | { 87 | return 'message'; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | setRiskyAllowed(true) 14 | ->setRules([ 15 | '@PSR2' => true, 16 | '@Symfony' => true, 17 | '@DoctrineAnnotation' => true, 18 | '@PhpCsFixer' => true, 19 | 'header_comment' => [ 20 | 'comment_type' => 'PHPDoc', 21 | 'header' => $header, 22 | 'separate' => 'none', 23 | 'location' => 'after_declare_strict', 24 | ], 25 | 'array_syntax' => [ 26 | 'syntax' => 'short' 27 | ], 28 | 'list_syntax' => [ 29 | 'syntax' => 'short' 30 | ], 31 | 'concat_space' => [ 32 | 'spacing' => 'one' 33 | ], 34 | 'blank_line_before_statement' => [ 35 | 'statements' => [ 36 | 'declare', 37 | ], 38 | ], 39 | 'general_phpdoc_annotation_remove' => [ 40 | 'annotations' => [ 41 | 'author' 42 | ], 43 | ], 44 | 'ordered_imports' => [ 45 | 'imports_order' => [ 46 | 'class', 'function', 'const', 47 | ], 48 | 'sort_algorithm' => 'alpha', 49 | ], 50 | 'single_line_comment_style' => [ 51 | 'comment_types' => [ 52 | ], 53 | ], 54 | 'yoda_style' => [ 55 | 'always_move_variable' => false, 56 | 'equal' => false, 57 | 'identical' => false, 58 | ], 59 | 'phpdoc_align' => [ 60 | 'align' => 'left', 61 | ], 62 | 'multiline_whitespace_before_semicolons' => [ 63 | 'strategy' => 'no_multi_line', 64 | ], 65 | 'constant_case' => [ 66 | 'case' => 'lower', 67 | ], 68 | 'global_namespace_import' => [ 69 | 'import_classes' => true, 70 | 'import_constants' => true, 71 | 'import_functions' => true, 72 | ], 73 | 'class_attributes_separation' => true, 74 | 'combine_consecutive_unsets' => true, 75 | 'declare_strict_types' => true, 76 | 'linebreak_after_opening_tag' => true, 77 | 'lowercase_static_reference' => true, 78 | 'no_useless_else' => true, 79 | 'no_unused_imports' => true, 80 | 'not_operator_with_successor_space' => true, 81 | 'not_operator_with_space' => false, 82 | 'ordered_class_elements' => true, 83 | 'php_unit_strict' => false, 84 | 'phpdoc_separation' => false, 85 | 'single_quote' => true, 86 | 'standardize_not_equals' => true, 87 | 'multiline_comment_opening_closing' => true, 88 | ]) 89 | ->setFinder( 90 | PhpCsFixer\Finder::create() 91 | ->exclude('vendor') 92 | ->in(__DIR__) 93 | ) 94 | ->setUsingCache(false); 95 | -------------------------------------------------------------------------------- /src/Contact/Contact.php: -------------------------------------------------------------------------------- 1 | request('GET', 'open-apis/contact/v3/unit/' . $id); 34 | 35 | return $ret['data'] ?? []; 36 | } 37 | 38 | /** 39 | * 获取部门信息. 40 | * @param $extra = [ 41 | * 'user_id_type' => 'open_id', // open_id, user_id, union_id 42 | * 'department_id_type' => 'open_department_id', // open_department_id, department_id 43 | * ] 44 | */ 45 | public function department(string $id, array $extra = []) 46 | { 47 | $ret = $this->request('GET', 'open-apis/contact/v3/departments/' . $id, [ 48 | RequestOptions::QUERY => $extra, 49 | ]); 50 | 51 | return $ret['data'] ?? []; 52 | } 53 | 54 | /** 55 | * 获取子部门列表. 56 | * @param $extra = [ 57 | * 'user_id_type' => 'open_id', // open_id, user_id, union_id 58 | * 'department_id_type' => 'open_department_id', // open_department_id, department_id 59 | * 'fetch_child' => false, // 是否递归获取子部门 60 | * 'page_size' => 10, // 分页大小 61 | * 'page_token' => '', // 分页TOKEN 62 | * ] 63 | */ 64 | public function departmentChildren(string $id, array $extra = []) 65 | { 66 | $ret = $this->request('GET', 'open-apis/contact/v3/departments/' . $id . '/children', [ 67 | RequestOptions::QUERY => $extra, 68 | ]); 69 | 70 | return $ret['data'] ?? []; 71 | } 72 | 73 | /** 74 | * 获取用户信息. 75 | */ 76 | public function user(string $id, string $type = 'open_id') 77 | { 78 | $ret = $this->request('GET', 'open-apis/contact/v3/users/' . $id, [ 79 | RequestOptions::QUERY => [ 80 | 'user_id_type' => $type, 81 | ], 82 | ]); 83 | 84 | return $ret['data'] ?? []; 85 | } 86 | 87 | /** 88 | * 获取部门下的用户. 89 | * @param $extra = [ 90 | * 'user_id_type' => 'open_id', // open_id, user_id, union_id 91 | * 'department_id_type' => 'open_department_id', // open_department_id, department_id 92 | * 'page_size' => 10, // 分页大小 93 | * 'page_token' => '', // 分页TOKEN 94 | * ] 95 | */ 96 | public function usersByDepartment(string $id, array $extra = []) 97 | { 98 | $ret = $this->request('GET', 'open-apis/contact/v3/users/find_by_department', [ 99 | RequestOptions::QUERY => array_merge($extra, [ 100 | 'department_id' => $id, 101 | ]), 102 | ]); 103 | 104 | return $ret['data'] ?? []; 105 | } 106 | 107 | /** 108 | * 批量获取用户ID. 109 | */ 110 | public function batchGetUserId(array $mobiles = [], array $emails = [], string $type = 'open_id') 111 | { 112 | $ret = $this->request('POST', 'open-apis/contact/v3/users/batch_get_id', [ 113 | RequestOptions::QUERY => [ 114 | 'user_id_type' => $type, 115 | ], 116 | RequestOptions::JSON => [ 117 | 'mobiles' => $mobiles, 118 | 'emails' => $emails, 119 | ], 120 | ]); 121 | 122 | return $ret['data'] ?? []; 123 | } 124 | 125 | public static function getName(): string 126 | { 127 | return 'contact'; 128 | } 129 | } 130 | --------------------------------------------------------------------------------