1"""
2Module for sending messages to Slack
3
4.. versionadded:: 2015.5.0
5
6:configuration: This module can be used by either passing an api key and version
7    directly or by specifying both in a configuration profile in the salt
8    master/minion config.
9
10    For example:
11
12    .. code-block:: yaml
13
14        slack:
15          api_key: peWcBiMOS9HrZG15peWcBiMOS9HrZG15
16"""
17
18
19import logging
20import urllib.parse
21
22import salt.utils.json
23import salt.utils.slack
24from salt.exceptions import SaltInvocationError
25
26log = logging.getLogger(__name__)
27
28__virtualname__ = "slack"
29
30
31def __virtual__():
32    """
33    Return virtual name of the module.
34
35    :return: The virtual name of the module.
36    """
37    return __virtualname__
38
39
40def _get_api_key():
41    api_key = __salt__["config.get"]("slack.api_key") or __salt__["config.get"](
42        "slack:api_key"
43    )
44
45    if not api_key:
46        raise SaltInvocationError("No Slack API key found.")
47
48    return api_key
49
50
51def _get_hook_id():
52    url = __salt__["config.get"]("slack.hook") or __salt__["config.get"]("slack:hook")
53    if not url:
54        raise SaltInvocationError("No Slack WebHook url found")
55
56    return url
57
58
59def list_rooms(api_key=None):
60    """
61    List all Slack rooms.
62
63    :param api_key: The Slack admin api key.
64    :return: The room list.
65
66    CLI Example:
67
68    .. code-block:: bash
69
70        salt '*' slack.list_rooms
71
72        salt '*' slack.list_rooms api_key=peWcBiMOS9HrZG15peWcBiMOS9HrZG15
73    """
74    if not api_key:
75        api_key = _get_api_key()
76    return salt.utils.slack.query(function="rooms", api_key=api_key, opts=__opts__)
77
78
79def list_users(api_key=None):
80    """
81    List all Slack users.
82
83    :param api_key: The Slack admin api key.
84    :return: The user list.
85
86    CLI Example:
87
88    .. code-block:: bash
89
90        salt '*' slack.list_users
91
92        salt '*' slack.list_users api_key=peWcBiMOS9HrZG15peWcBiMOS9HrZG15
93    """
94    if not api_key:
95        api_key = _get_api_key()
96    return salt.utils.slack.query(function="users", api_key=api_key, opts=__opts__)
97
98
99def find_room(name, api_key=None):
100    """
101    Find a room by name and return it.
102
103    :param name:    The room name.
104    :param api_key: The Slack admin api key.
105    :return:        The room object.
106
107    CLI Example:
108
109    .. code-block:: bash
110
111        salt '*' slack.find_room name="random"
112
113        salt '*' slack.find_room name="random" api_key=peWcBiMOS9HrZG15peWcBiMOS9HrZG15
114    """
115    if not api_key:
116        api_key = _get_api_key()
117
118    # search results don't include the name of the
119    # channel with a hash, if the passed channel name
120    # has a hash we remove it.
121    if name.startswith("#"):
122        name = name[1:]
123    ret = list_rooms(api_key)
124    if ret["res"]:
125        rooms = ret["message"]
126        if rooms:
127            for room in rooms:
128                if room["name"] == name:
129                    return room
130    return False
131
132
133def find_user(name, api_key=None):
134    """
135    Find a user by name and return it.
136
137    :param name:        The user name.
138    :param api_key:     The Slack admin api key.
139    :return:            The user object.
140
141    CLI Example:
142
143    .. code-block:: bash
144
145        salt '*' slack.find_user name="ThomasHatch"
146
147        salt '*' slack.find_user name="ThomasHatch" api_key=peWcBiMOS9HrZG15peWcBiMOS9HrZG15
148    """
149    if not api_key:
150        api_key = _get_api_key()
151
152    ret = list_users(api_key)
153    if ret["res"]:
154        users = ret["message"]
155        if users:
156            for user in users:
157                if user["name"] == name:
158                    return user
159    return False
160
161
162def post_message(
163    channel,
164    message,
165    from_name,
166    api_key=None,
167    icon=None,
168    attachments=None,
169    blocks=None,
170):
171    """
172    Send a message to a Slack channel.
173
174    .. versionchanged:: 3003
175        Added `attachments` and `blocks` kwargs
176
177    :param channel:     The channel name, either will work.
178    :param message:     The message to send to the Slack channel.
179    :param from_name:   Specify who the message is from.
180    :param api_key:     The Slack api key, if not specified in the configuration.
181    :param icon:        URL to an image to use as the icon for this message
182    :param attachments: Any attachments to be sent with the message.
183    :param blocks:      Any blocks to be sent with the message.
184    :return:            Boolean if message was sent successfully.
185
186    CLI Example:
187
188    .. code-block:: bash
189
190        salt '*' slack.post_message channel="Development Room" message="Build is done" from_name="Build Server"
191
192    """
193    if not api_key:
194        api_key = _get_api_key()
195
196    if not channel:
197        log.error("channel is a required option.")
198
199    # channel must start with a hash or an @ (direct-message channels)
200    if not channel.startswith("#") and not channel.startswith("@"):
201        log.warning(
202            "Channel name must start with a hash or @. "
203            'Prepending a hash and using "#%s" as '
204            "channel name instead of %s",
205            channel,
206            channel,
207        )
208        channel = "#{}".format(channel)
209
210    if not from_name:
211        log.error("from_name is a required option.")
212
213    if not message:
214        log.error("message is a required option.")
215
216    if not from_name:
217        log.error("from_name is a required option.")
218
219    parameters = {
220        "channel": channel,
221        "username": from_name,
222        "text": message,
223        "attachments": attachments or [],
224        "blocks": blocks or [],
225    }
226
227    if icon is not None:
228        parameters["icon_url"] = icon
229
230    # Slack wants the body on POST to be urlencoded.
231    result = salt.utils.slack.query(
232        function="message",
233        api_key=api_key,
234        method="POST",
235        header_dict={"Content-Type": "application/x-www-form-urlencoded"},
236        data=urllib.parse.urlencode(parameters),
237        opts=__opts__,
238    )
239
240    if result["res"]:
241        return True
242    else:
243        return result
244
245
246def call_hook(
247    message,
248    attachment=None,
249    color="good",
250    short=False,
251    identifier=None,
252    channel=None,
253    username=None,
254    icon_emoji=None,
255):
256    """
257    Send message to Slack incoming webhook.
258
259    :param message:     The topic of message.
260    :param attachment:  The message to send to the Slack WebHook.
261    :param color:       The color of border of left side
262    :param short:       An optional flag indicating whether the value is short
263                        enough to be displayed side-by-side with other values.
264    :param identifier:  The identifier of WebHook.
265    :param channel:     The channel to use instead of the WebHook default.
266    :param username:    Username to use instead of WebHook default.
267    :param icon_emoji:  Icon to use instead of WebHook default.
268    :return:            Boolean if message was sent successfully.
269
270    CLI Example:
271
272    .. code-block:: bash
273
274        salt '*' slack.call_hook message='Hello, from SaltStack'
275
276    """
277    base_url = "https://hooks.slack.com/services/"
278    if not identifier:
279        identifier = _get_hook_id()
280
281    url = urllib.parse.urljoin(base_url, identifier)
282
283    if not message:
284        log.error("message is required option")
285
286    if attachment:
287        payload = {
288            "attachments": [
289                {
290                    "fallback": message,
291                    "color": color,
292                    "pretext": message,
293                    "fields": [{"value": attachment, "short": short}],
294                }
295            ]
296        }
297    else:
298        payload = {
299            "text": message,
300        }
301
302    if channel:
303        payload["channel"] = channel
304
305    if username:
306        payload["username"] = username
307
308    if icon_emoji:
309        payload["icon_emoji"] = icon_emoji
310
311    data = urllib.parse.urlencode({"payload": salt.utils.json.dumps(payload)})
312    result = salt.utils.http.query(url, method="POST", data=data, status=True)
313
314    if result["status"] <= 201:
315        return True
316    else:
317        return {"res": False, "message": result.get("body", result["status"])}
318