From afb2e4ba36d978d06d5308ebdd14c4e3cd3293f9 Mon Sep 17 00:00:00 2001 From: "Connor S. Parks" Date: Sun, 28 Sep 2014 17:58:11 +0100 Subject: [PATCH] initial commit --- README.md | 46 ++++ composer.json | 25 +++ src/Contracts/Http/Interactor.php | 34 +++ src/Contracts/Http/Response.php | 26 +++ src/Contracts/Http/ResponseFactory.php | 15 ++ src/Core/Commander.php | 300 +++++++++++++++++++++++++ src/Http/CurlInteractor.php | 106 +++++++++ src/Http/SlackResponse.php | 84 +++++++ src/Http/SlackResponseFactory.php | 13 ++ 9 files changed, 649 insertions(+) create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/Contracts/Http/Interactor.php create mode 100644 src/Contracts/Http/Response.php create mode 100644 src/Contracts/Http/ResponseFactory.php create mode 100644 src/Core/Commander.php create mode 100644 src/Http/CurlInteractor.php create mode 100644 src/Http/SlackResponse.php create mode 100644 src/Http/SlackResponseFactory.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d86104 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +PHP Slack +========= + +> A lightweight PHP implementation of Slack's API. + +### Provides + +* Frlnc\Slack\Contracts + + A small set of contracts to allow for the consumption of the Slack API. **Interactor**, **Response** and **ResponseFactory**. + + * **Interactor** is in charge of providing the Http GET/POST methods. + * **Response** is in charge of providing a simple Http response wrapper for holding the body, headers and status code. + * **ResponseFactory** is in charge of providing a factory to instantiate and build the **Response**. + +To use this package, it's simple. Though _please note_ that this implementation is very lightweight meaning you'll need to do some more work than usual. This package doesn't provide methods such as `Chat::postMessage(string message)`, it literally provides one method (`Commander::execute(string command, array parameters = [])`). + +Here is a very simple example of using this package: +```php +setResponseFactory(new SlackResponseFactory); + +$commander = new Commander('xoxp-some-token-for-slack', $interactor); + +$response = $commander->execute('chat.postMessage', [ + 'channel' => '#general', + 'text' => 'Hello, world!' +]); + +if ($response['ok']) +{ + // Command worked +} +else +{ + // Command didn't work +} +``` + +Note that Commander will automatically format most inputs to Slack's requirements but attachments are not supported - you will need to manually call `$text = Commander::format($text)` to convert it. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e025608 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "frlnc/php-slack", + "description": "A lightweight PHP implementation of Slack's API.", + "keywords": ["slack"], + "license": "MIT", + "authors": [ + { + "name": "Connor Parks", + "email": "connor@connorvg.tv" + } + ], + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": { + "Frlnc\\Slack\\": "src/" + } + }, + "minimum-stability": "dev" +} diff --git a/src/Contracts/Http/Interactor.php b/src/Contracts/Http/Interactor.php new file mode 100644 index 0000000..db51244 --- /dev/null +++ b/src/Contracts/Http/Interactor.php @@ -0,0 +1,34 @@ + [ + 'endpoint' => '/api.test', + 'token' => false + ], + 'auth.test' => [ + 'endpoint' => '/auth.test', + 'token' => true + ], + 'channels.history' => [ + 'token' => true, + 'endpoint' => '/channels.history' + ], + 'channels.info' => [ + 'token' => true, + 'endpoint' => '/channels.info' + ], + 'channels.invite' => [ + 'token' => true, + 'endpoint' => '/channels.invite' + ], + 'channels.join' => [ + 'token' => true, + 'endpoint' => '/channels.join' + ], + 'channels.kick' => [ + 'token' => true, + 'endpoint' => '/channels.kick' + ], + 'channels.leave' => [ + 'token' => true, + 'endpoint' => '/channels.leave' + ], + 'channels.list' => [ + 'token' => true, + 'endpoint' => '/channels.list' + ], + 'channels.mark' => [ + 'token' => true, + 'endpoint' => '/channels.mark' + ], + 'channels.setPurpose' => [ + 'token' => true, + 'endpoint' => '/channels.setPurpose', + 'format' => [ + 'purpose' + ] + ], + 'channels.setTopic' => [ + 'token' => true, + 'endpoint' => '/channels.setTopic', + 'format' => [ + 'topic' + ] + ], + 'chat.delete' => [ + 'token' => true, + 'endpoint' => '/chat.delete' + ], + 'chat.postMessage' => [ + 'token' => true, + 'endpoint' => '/chat.postMessage', + 'format' => [ + 'text', + 'username' + ] + ], + 'chat.update' => [ + 'token' => true, + 'endpoint' => '/chat.update', + 'format' => [ + 'text' + ] + ], + 'emoji.list' => [ + 'token' => true, + 'endpoint' => '/emoji.list' + ], + 'files.info' => [ + 'token' => true, + 'endpoint' => '/files.info' + ], + 'files.list' => [ + 'token' => true, + 'endpoint' => '/files.list' + ], + 'files.upload' => [ + 'token' => true, + 'endpoint' => '/files.upload', + 'post' => true, + 'headers' => [ + 'Content-Type' => 'multipart/form-data' + ], + 'format' => [ + 'filename', + 'title', + 'initial_comment' + ] + ], + 'groups.create' => [ + 'token' => true, + 'endpoint' => '/groups.create', + 'format' => [ + 'name' + ] + ], + 'groups.createChild' => [ + 'token' => true, + 'endpoint' => '/groups.createChild' + ], + 'groups.history' => [ + 'token' => true, + 'endpoint' => '/groups.history' + ], + 'groups.invite' => [ + 'token' => true, + 'endpoint' => '/groups.invite' + ], + 'groups.kick' => [ + 'token' => true, + 'endpoint' => '/groups.kick' + ], + 'groups.leave' => [ + 'token' => true, + 'endpoint' => '/groups.leave' + ], + 'groups.list' => [ + 'token' => true, + 'endpoint' => '/groups.list' + ], + 'groups.mark' => [ + 'token' => true, + 'endpoint' => '/groups.mark' + ], + 'groups.setPurpose' => [ + 'token' => true, + 'endpoint' => '/groups.setPurpose', + 'format' => [ + 'purpose' + ] + ], + 'groups.setTopic' => [ + 'token' => true, + 'endpoint' => '/groups.setTopic', + 'format' => [ + 'topic' + ] + ], + 'im.history' => [ + 'token' => true, + 'endpoint' => '/im.history' + ], + 'im.list' => [ + 'token' => true, + 'endpoint' => '/im.list' + ], + 'im.mark' => [ + 'token' => true, + 'endpoint' => '/im.mark' + ], + 'oauth.access' => [ + 'token' => false, + 'endpoint' => '/oauth.access' + ], + 'search.all' => [ + 'token' => true, + 'endpoint' => '/search.all' + ], + 'search.files' => [ + 'token' => true, + 'endpoint' => '/search.files' + ], + 'search.messages' => [ + 'token' => true, + 'endpoint' => '/search.messages' + ], + 'stars.list' => [ + 'token' => true, + 'endpoint' => '/stars.list' + ], + 'users.list' => [ + 'token' => true, + 'endpoint' => '/users.list' + ], + 'users.setActive' => [ + 'token' => true, + 'endpoint' => '/users.setActive' + ] + ]; + + /** + * The base URL. + * + * @var string + */ + protected static $baseUrl = 'https://slack.com/api'; + + /** + * The API token. + * + * @var string + */ + protected $token; + + /** + * The Http interactor. + * + * @var \Frlnc\Slack\Contracts\Http\Interactor + */ + protected $interactor; + + /** + * @param string $token + * @param \Frlnc\Slack\Contracts\Http\Interactor $interactor + */ + public function __construct($token, Interactor $interactor) + { + $this->token = $token; + $this->interactor = $interactor; + } + + /** + * Executes a command. + * + * @param string $command + * @param array $parameters + * @return \Frlnc\Slack\Contracts\Http\Response + */ + public function execute($command, array $parameters = []) + { + if (!isset(self::$commands[$command])) + throw new InvalidArgumentException("The command '{$command}' is not currently supported"); + + $command = self::$commands[$command]; + + if ($command['token']) + $parameters = array_merge($parameters, ['token' => $this->token]); + + if (isset($command['format'])) + foreach ($command['format'] as $format) + if (isset($parameters[$format])) + $parameters[$format] = self::format($parameters[$format]); + + $headers = []; + if (isset($command['headers'])) + $headers = $command['headers']; + + $url = self::$baseUrl . $command['endpoint']; + + if (isset($command['post']) && $command['post']) + return $this->interactor->post($url, [], $parameters, $headers); + + return $this->interactor->get($url, $parameters, $headers); + } + + /** + * Sets the token. + * + * @param string $token + */ + public function setToken($token) + { + $this->token = $token; + } + + /** + * Formats a string for Slack. + * + * @param string $string + * @return string + */ + public static function format($string) + { + $string = str_replace('&', '&', $string); + $string = str_replace('<', '<', $string); + $string = str_replace('>', '>', $string); + + return $string; + } + +} diff --git a/src/Http/CurlInteractor.php b/src/Http/CurlInteractor.php new file mode 100644 index 0000000..a29825a --- /dev/null +++ b/src/Http/CurlInteractor.php @@ -0,0 +1,106 @@ +prepareRequest($url, $parameters, $headers); + + return $this->executeRequest($request); + } + + /** + * {@inheritdoc} + */ + public function post($url, array $urlParameters = [], array $postParameters = [], array $headers = []) + { + $request = $this->prepareRequest($url, $urlParameters, $headers); + + curl_setopt($request, CURLOPT_POST, count($postParameters)); + curl_setopt($request, CURLOPT_POSTFIELDS, http_build_query($postParameters)); + + return $this->executeRequest($request); + } + + /** + * Prepares a request using curl. + * + * @param string $url [description] + * @param array $parameters [description] + * @param array $headers [description] + * @return resource + */ + protected static function prepareRequest($url, $parameters = [], $headers = []) + { + $request = curl_init(); + + if ($query = http_build_query($parameters)) + $url .= '?' . $query; + + curl_setopt($request, CURLOPT_URL, $url); + curl_setopt($request, CURLOPT_RETURNTRANSFER, true); + curl_setopt($request, CURLOPT_HTTPHEADER, $headers); + curl_setopt($request, CURLINFO_HEADER_OUT, true); + curl_setopt($request, CURLOPT_SSL_VERIFYPEER, false); + + return $request; + } + + /** + * Executes a curl request. + * + * @param resource $request + * @return \Frlnc\Slack\Contracts\Http\Response + */ + public function executeRequest($request) + { + $body = curl_exec($request); + $info = curl_getinfo($request); + + curl_close($request); + + $statusCode = $info['http_code']; + $headers = $info['request_header']; + + if (function_exists('http_parse_headers')) + $headers = http_parse_headers($headers); + else + { + $header_text = substr($headers, 0, strpos($headers, "\r\n\r\n")); + $headers = []; + + foreach (explode("\r\n", $header_text) as $i => $line) + if ($i === 0) + continue; + else + { + list ($key, $value) = explode(': ', $line); + + $headers[$key] = $value; + } + } + + return $this->factory->build($body, $headers, $statusCode); + } + + /** + * {@inheritdoc} + */ + public function setResponseFactory(ResponseFactory $factory) + { + $this->factory = $factory; + } + +} diff --git a/src/Http/SlackResponse.php b/src/Http/SlackResponse.php new file mode 100644 index 0000000..dccfdfb --- /dev/null +++ b/src/Http/SlackResponse.php @@ -0,0 +1,84 @@ +body = json_decode($body, true); + $this->headers = $headers; + $this->statusCode = $statusCode; + } + + /** + * {@inheritdoc} + */ + public function getBody() + { + return $this->body; + } + + /** + * {@inheritdoc} + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * {@inheritdoc} + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * Converts the response to an array. + * + * @return array + */ + public function toArray() + { + return [ + 'status_code' => $this->getStatusCode(), + 'headers' => $this->getHeaders(), + 'body' => $this->getBody() + ]; + } + +} diff --git a/src/Http/SlackResponseFactory.php b/src/Http/SlackResponseFactory.php new file mode 100644 index 0000000..c61cbc5 --- /dev/null +++ b/src/Http/SlackResponseFactory.php @@ -0,0 +1,13 @@ +