Passed
Pull Request — master (#53)
by
unknown
01:19
created

Client::getBlocksAsArrays()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Nexylan packages.
7
 *
8
 * (c) Nexylan SAS <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Nexy\Slack;
15
16
use Http\Client\Common\HttpMethodsClient;
17
use Http\Client\Common\Plugin\BaseUriPlugin;
18
use Http\Client\Common\Plugin\HeaderAppendPlugin;
19
use Http\Client\Common\PluginClient;
20
use Http\Client\Exception;
21
use Http\Client\HttpClient;
22
use Http\Discovery\HttpClientDiscovery;
23
use Http\Discovery\MessageFactoryDiscovery;
24
use Http\Discovery\UriFactoryDiscovery;
25
use Nexy\Slack\Exception\SlackErrorException;
26
use Symfony\Component\OptionsResolver\OptionsResolver;
27
use Psr\Http\Message\ResponseInterface;
28
29
/**
30
 * @author Sullivan Senechal <[email protected]>
31
 */
32
final class Client
33
{
34
    const SLACK_POST_MESSAGE_URL = 'https://slack.com/api/chat.postMessage';
35
36
    /**
37
     * @var array
38
     */
39
    private $options;
40
41
    /**
42
     * @var HttpMethodsClient
43
     */
44
    private $httpClient;
45
46
    private $optionsResolver;
47
48
    /**
49
     * Instantiate a new Client.
50
     *
51
     * @param string          $endpoint
52
     * @param array           $options
53
     * @param HttpClient|null $httpClient
54
     */
55
    public function __construct(string $endpoint = self::SLACK_POST_MESSAGE_URL, array $options = [], HttpClient $httpClient = null)
56
    {
57
        $this->optionsResolver = (new OptionsResolver())
58
            ->setDefaults([
59
                'channel' => null,
60
                'sticky_channel' => false,
61
                'username' => null,
62
                'icon' => null,
63
                'link_names' => false,
64
                'unfurl_links' => false,
65
                'unfurl_media' => true,
66
                'allow_markdown' => true,
67
                'markdown_in_attachments' => [],
68
                'oauth_token' => null
69
            ])
70
            ->setAllowedTypes('channel', ['string', 'null'])
71
            ->setAllowedTypes('sticky_channel', ['bool'])
72
            ->setAllowedTypes('username', ['string', 'null'])
73
            ->setAllowedTypes('icon', ['string', 'null'])
74
            ->setAllowedTypes('link_names', 'bool')
75
            ->setAllowedTypes('unfurl_links', 'bool')
76
            ->setAllowedTypes('unfurl_media', 'bool')
77
            ->setAllowedTypes('allow_markdown', 'bool')
78
            ->setAllowedTypes('markdown_in_attachments', 'array')
79
            ->setAllowedTypes('oauth_token', ['string', 'null'])
80
        ;
81
82
        $this->setOptions($options, $endpoint, $httpClient);
83
    }
84
85
    public function setEndpoint($endpoint, HttpClient $httpClient = null): self
86
    {
87
        $plugins = [
88
            new BaseUriPlugin(
89
                UriFactoryDiscovery::find()->createUri($endpoint)
90
            ),
91
        ];
92
93
        if ($this->options['oauth_token']) {
94
            $plugins[] = new HeaderAppendPlugin([
95
                'Authorization' => 'Bearer ' . $this->options['oauth_token'],
96
                'Content-Type' => 'application/json'
97
            ]);
98
        }
99
100
        $this->httpClient = new HttpMethodsClient(
101
            new PluginClient(
102
                $httpClient ?: HttpClientDiscovery::find(),
103
                $plugins
104
            ),
105
            MessageFactoryDiscovery::find()
106
        );
107
        return $this;
108
    }
109
110
    public function setOptions(array $options, string $endpoint = self::SLACK_POST_MESSAGE_URL, HttpClient $httpClient = null): self
111
    {
112
        $this->options = $this->optionsResolver->resolve($options);
113
        $this->setEndpoint($endpoint, $httpClient);
114
        return $this;
115
    }
116
117
    /**
118
     * Pass any unhandled methods through to a new Message
119
     * instance.
120
     *
121
     * @param string $name      The name of the method
122
     * @param array  $arguments The method arguments
123
     *
124
     * @return \Nexy\Slack\Message
125
     */
126
    public function __call(string $name, array $arguments): Message
127
    {
128
        return \call_user_func_array([$this->createMessage(), $name], $arguments);
129
    }
130
131
    /**
132
     * @return array
133
     */
134
    public function getOptions(): array
135
    {
136
        return $this->options;
137
    }
138
139
    /**
140
     * Create a new message with defaults.
141
     *
142
     * @return \Nexy\Slack\Message
143
     */
144
    public function createMessage(): Message
145
    {
146
        return (new Message($this))
147
            ->setChannel($this->options['channel'])
148
            ->setUsername($this->options['username'])
149
            ->setIcon($this->options['icon'])
150
            ->setAllowMarkdown($this->options['allow_markdown'])
151
            ->setMarkdownInAttachments($this->options['markdown_in_attachments'])
152
        ;
153
    }
154
155
    /**
156
     * Send a message.
157
     *
158
     * @param \Nexy\Slack\Message $message
159
     *
160
     * @throws Exception
161
     */
162
    public function sendMessage(Message $message): void
163
    {
164
        // Ensure the message will always be sent to the default channel if asked for.
165
        if ($this->options['sticky_channel']) {
166
            $message->setChannel($this->options['channel']);
167
        }
168
169
        $payload = $this->preparePayload($message);
170
171
        $encoded = \json_encode($payload, JSON_UNESCAPED_UNICODE);
172
173
        if (false === $encoded) {
174
            throw new \RuntimeException(\sprintf('JSON encoding error %s: %s', \json_last_error(), \json_last_error_msg()));
175
        }
176
177
        $response = $this->httpClient->post('', [], $encoded);
178
179
        if ($this->isErrorResponse($response)) {
180
            throw new SlackErrorException($response);
181
        }
182
    }
183
184
    protected function isErrorResponse(ResponseInterface $response)
185
    {
186
        $data = json_decode($response->getBody()->getContents(), true);
187
        $response->getBody()->rewind();
188
189
        return $response->getStatusCode() !== 200 || !$data['ok'];
190
    }
191
192
    /**
193
     * Prepares the payload to be sent to the webhook.
194
     *
195
     * @param \Nexy\Slack\Message $message The message to send
196
     *
197
     * @return array
198
     */
199
    private function preparePayload(Message $message): array
200
    {
201
        $payload = [
202
            'text' => $message->getText(),
203
            'channel' => $message->getChannel(),
204
            'username' => $message->getUsername(),
205
            'link_names' => $this->options['link_names'] ? 1 : 0,
206
            'unfurl_links' => $this->options['unfurl_links'],
207
            'unfurl_media' => $this->options['unfurl_media'],
208
            'mrkdwn' => $this->options['allow_markdown'],
209
        ];
210
211
        if ($icon = $message->getIcon()) {
212
            $payload[$message->getIconType()] = $icon;
213
        }
214
215
        $payload['attachments'] = $this->getAttachmentsAsArrays($message);
216
        $payload['blocks'] = $this->getBlocksAsArrays($message);
217
218
        return $payload;
219
    }
220
221
    /**
222
     * Get the attachments in array form.
223
     *
224
     * @param \Nexy\Slack\Message $message
225
     *
226
     * @return array
227
     */
228
    private function getAttachmentsAsArrays(Message $message): array
229
    {
230
        $attachments = [];
231
232
        foreach ($message->getAttachments() as $attachment) {
233
            $attachments[] = $attachment->toArray();
234
        }
235
236
        return $attachments;
237
    }
238
239
    /**
240
     * Get the attachments in array form.
241
     *
242
     * @param \Nexy\Slack\Message $message
243
     *
244
     * @return array
245
     */
246
    private function getBlocksAsArrays(Message $message): array
247
    {
248
        $blocks = [];
249
250
        foreach ($message->getBlocks() as $block) {
251
            $blocks[] = $block->toArray();
252
        }
253
254
        return $blocks;
255
    }
256
}
257