├── requirements.txt
├── static
└── slacker.jpg
├── .gitignore
├── setup.py
├── README.rst
├── LICENSE
└── slacker
└── __init__.py
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests >= 2.2.1
2 |
--------------------------------------------------------------------------------
/static/slacker.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Instagram/slacker/HEAD/static/slacker.jpg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac OS
2 | .DS_Store
3 |
4 | # Python
5 | *.pyc
6 | *.pyo
7 | dist
8 | slacker.egg-info
9 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 |
4 | setup(name='slacker',
5 | version='0.3.7',
6 | packages=['slacker'],
7 | description='Slack API client',
8 | author='Oktay Sancak',
9 | author_email='oktaysancak@gmail.com',
10 | url='http://github.com/os/slacker/',
11 | install_requires=['requests >= 2.2.1'],
12 | license='http://www.apache.org/licenses/LICENSE-2.0',
13 | keywords='slack api')
14 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | =======
2 | Slacker
3 | =======
4 |
5 | .. image:: https://raw.githubusercontent.com/os/slacker/master/static/slacker.jpg
6 |
7 | About
8 | =====
9 | Slacker is a full-featured Python interface for the `Slack API
10 | `_.
11 |
12 | Examples
13 | ========
14 | .. code-block:: python
15 |
16 | from slacker import Slacker
17 |
18 | slack = Slacker('')
19 |
20 | # Send a message to #general channel
21 | slack.chat.post_message('#general', 'Hello fellow slackers!')
22 |
23 | # Get users list
24 | response = slack.users.list()
25 | users = response.body['members']
26 |
27 | # Upload a file
28 | slack.files.upload('hello.txt')
29 |
30 | Installation
31 | ============
32 | .. code-block:: bash
33 |
34 | $ pip install slacker
35 |
36 | Documentation
37 | =============
38 | https://api.slack.com/
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the
13 | copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other
16 | entities that control, are controlled by, or are under common control with
17 | that entity. For the purposes of this definition, "control" means (i) the
18 | power, direct or indirect, to cause the direction or management of such
19 | entity, whether by contract or otherwise, or (ii) ownership of
20 | fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
21 | ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity exercising
24 | permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation source,
28 | and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical transformation
31 | or translation of a Source form, including but not limited to compiled
32 | object code, generated documentation, and conversions to
33 | other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or Object
36 | form, made available under the License, as indicated by a copyright notice
37 | that is included in or attached to the work (an example is provided in the
38 | Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object form,
41 | that is based on (or derived from) the Work and for which the editorial
42 | revisions, annotations, elaborations, or other modifications represent,
43 | as a whole, an original work of authorship. For the purposes of this
44 | License, Derivative Works shall not include works that remain separable
45 | from, or merely link (or bind by name) to the interfaces of, the Work and
46 | Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including the original
49 | version of the Work and any modifications or additions to that Work or
50 | Derivative Works thereof, that is intentionally submitted to Licensor for
51 | inclusion in the Work by the copyright owner or by an individual or
52 | Legal Entity authorized to submit on behalf of the copyright owner.
53 | For the purposes of this definition, "submitted" means any form of
54 | electronic, verbal, or written communication sent to the Licensor or its
55 | representatives, including but not limited to communication on electronic
56 | mailing lists, source code control systems, and issue tracking systems
57 | that are managed by, or on behalf of, the Licensor for the purpose of
58 | discussing and improving the Work, but excluding communication that is
59 | conspicuously marked or otherwise designated in writing by the copyright
60 | owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity on
63 | behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License.
67 |
68 | Subject to the terms and conditions of this License, each Contributor
69 | hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
70 | royalty-free, irrevocable copyright license to reproduce, prepare
71 | Derivative Works of, publicly display, publicly perform, sublicense,
72 | and distribute the Work and such Derivative Works in
73 | Source or Object form.
74 |
75 | 3. Grant of Patent License.
76 |
77 | Subject to the terms and conditions of this License, each Contributor
78 | hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
79 | royalty-free, irrevocable (except as stated in this section) patent
80 | license to make, have made, use, offer to sell, sell, import, and
81 | otherwise transfer the Work, where such license applies only to those
82 | patent claims licensable by such Contributor that are necessarily
83 | infringed by their Contribution(s) alone or by combination of their
84 | Contribution(s) with the Work to which such Contribution(s) was submitted.
85 | If You institute patent litigation against any entity (including a
86 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
87 | Contribution incorporated within the Work constitutes direct or
88 | contributory patent infringement, then any patent licenses granted to
89 | You under this License for that Work shall terminate as of the date such
90 | litigation is filed.
91 |
92 | 4. Redistribution.
93 |
94 | You may reproduce and distribute copies of the Work or Derivative Works
95 | thereof in any medium, with or without modifications, and in Source or
96 | Object form, provided that You meet the following conditions:
97 |
98 | 1. You must give any other recipients of the Work or Derivative Works a
99 | copy of this License; and
100 |
101 | 2. You must cause any modified files to carry prominent notices stating
102 | that You changed the files; and
103 |
104 | 3. You must retain, in the Source form of any Derivative Works that You
105 | distribute, all copyright, patent, trademark, and attribution notices from
106 | the Source form of the Work, excluding those notices that do not pertain
107 | to any part of the Derivative Works; and
108 |
109 | 4. If the Work includes a "NOTICE" text file as part of its distribution,
110 | then any Derivative Works that You distribute must include a readable copy
111 | of the attribution notices contained within such NOTICE file, excluding
112 | those notices that do not pertain to any part of the Derivative Works,
113 | in at least one of the following places: within a NOTICE text file
114 | distributed as part of the Derivative Works; within the Source form or
115 | documentation, if provided along with the Derivative Works; or, within a
116 | display generated by the Derivative Works, if and wherever such
117 | third-party notices normally appear. The contents of the NOTICE file are
118 | for informational purposes only and do not modify the License.
119 | You may add Your own attribution notices within Derivative Works that You
120 | distribute, alongside or as an addendum to the NOTICE text from the Work,
121 | provided that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and may
125 | provide additional or different license terms and conditions for use,
126 | reproduction, or distribution of Your modifications, or for any such
127 | Derivative Works as a whole, provided Your use, reproduction, and
128 | distribution of the Work otherwise complies with the conditions
129 | stated in this License.
130 |
131 | 5. Submission of Contributions.
132 |
133 | Unless You explicitly state otherwise, any Contribution intentionally
134 | submitted for inclusion in the Work by You to the Licensor shall be under
135 | the terms and conditions of this License, without any additional
136 | terms or conditions. Notwithstanding the above, nothing herein shall
137 | supersede or modify the terms of any separate license agreement you may
138 | have executed with Licensor regarding such Contributions.
139 |
140 | 6. Trademarks.
141 |
142 | This License does not grant permission to use the trade names, trademarks,
143 | service marks, or product names of the Licensor, except as required for
144 | reasonable and customary use in describing the origin of the Work and
145 | reproducing the content of the NOTICE file.
146 |
147 | 7. Disclaimer of Warranty.
148 |
149 | Unless required by applicable law or agreed to in writing, Licensor
150 | provides the Work (and each Contributor provides its Contributions)
151 | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
152 | either express or implied, including, without limitation, any warranties
153 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS
154 | FOR A PARTICULAR PURPOSE. You are solely responsible for determining the
155 | appropriateness of using or redistributing the Work and assume any risks
156 | associated with Your exercise of permissions under this License.
157 |
158 | 8. Limitation of Liability.
159 |
160 | In no event and under no legal theory, whether in tort
161 | (including negligence), contract, or otherwise, unless required by
162 | applicable law (such as deliberate and grossly negligent acts) or agreed
163 | to in writing, shall any Contributor be liable to You for damages,
164 | including any direct, indirect, special, incidental, or consequential
165 | damages of any character arising as a result of this License or out of
166 | the use or inability to use the Work (including but not limited to damages
167 | for loss of goodwill, work stoppage, computer failure or malfunction,
168 | or any and all other commercial damages or losses), even if such
169 | Contributor has been advised of the possibility of such damages.
170 |
171 | 9. Accepting Warranty or Additional Liability.
172 |
173 | While redistributing the Work or Derivative Works thereof, You may choose
174 | to offer, and charge a fee for, acceptance of support, warranty,
175 | indemnity, or other liability obligations and/or rights consistent with
176 | this License. However, in accepting such obligations, You may act only
177 | on Your own behalf and on Your sole responsibility, not on behalf of any
178 | other Contributor, and only if You agree to indemnify, defend, and hold
179 | each Contributor harmless for any liability incurred by, or claims
180 | asserted against, such Contributor by reason of your accepting any such
181 | warranty or additional liability.
182 |
183 | END OF TERMS AND CONDITIONS
184 |
--------------------------------------------------------------------------------
/slacker/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright 2014 Oktay Sancak
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | import json
16 |
17 | import requests
18 |
19 |
20 | API_BASE_URL = 'https://slack.com/api/{api}'
21 |
22 |
23 | __all__ = ['Error', 'Response', 'BaseAPI', 'API', 'Auth', 'Users', 'Groups',
24 | 'Channels', 'Chat', 'IM', 'Search', 'Files', 'Stars', 'Emoji',
25 | 'OAuth', 'Slacker']
26 |
27 |
28 | class Error(Exception):
29 | pass
30 |
31 |
32 | class Response(object):
33 | def __init__(self, body):
34 | self.raw = body
35 | self.body = json.loads(body)
36 | self.successful = self.body['ok']
37 | self.error = self.body.get('error')
38 |
39 |
40 | class BaseAPI(object):
41 | def __init__(self, token=None):
42 | self.token = token
43 |
44 | def _request(self, method, api, **kwargs):
45 | if self.token:
46 | kwargs.setdefault('params', {})['token'] = self.token
47 |
48 | response = method(API_BASE_URL.format(api=api),
49 | **kwargs)
50 |
51 | assert response.status_code == 200
52 |
53 | response = Response(response.text)
54 | if not response.successful:
55 | raise Error(response.error)
56 |
57 | return response
58 |
59 | def get(self, api, **kwargs):
60 | return self._request(requests.get, api, **kwargs)
61 |
62 | def post(self, api, **kwargs):
63 | return self._request(requests.post, api, **kwargs)
64 |
65 |
66 | class API(BaseAPI):
67 | def test(self, error=None, **kwargs):
68 | if error:
69 | kwargs['error'] = error
70 |
71 | return self.get('api.test', params=kwargs)
72 |
73 |
74 | class Auth(BaseAPI):
75 | def test(self):
76 | return self.get('auth.test')
77 |
78 |
79 | class Users(BaseAPI):
80 | def list(self):
81 | return self.get('users.list')
82 |
83 | def set_active(self):
84 | return self.post('users.setActive')
85 |
86 |
87 | class Groups(BaseAPI):
88 | def list(self, exclude_archived=None):
89 | return self.get('groups.list',
90 | params={'exclude_archived': exclude_archived})
91 |
92 | def history(self, channel, latest=None, oldest=None, count=None):
93 | return self.get('groups.history',
94 | params={
95 | 'channel': channel,
96 | 'latest': latest,
97 | 'oldest': oldest,
98 | 'count': count
99 | })
100 |
101 | def invite(self, channel, user):
102 | return self.post('groups.invite',
103 | params={'channel': channel, 'user': user})
104 |
105 | def kick(self, channel, user):
106 | return self.post('groups.kick',
107 | params={'channel': channel, 'user': user})
108 |
109 | def leave(self, channel):
110 | return self.post('groups.leave', params={'channel': channel})
111 |
112 | def mark(self, channel, ts):
113 | return self.post('groups.mark', params={'channel': channel, 'ts': ts})
114 |
115 | def set_purpose(self, channel, purpose):
116 | return self.post('groups.setPurpose',
117 | params={'channel': channel, 'purpose': purpose})
118 |
119 | def set_topic(self, channel, topic):
120 | return self.post('groups.setTopic',
121 | params={'channel': channel, 'topic': topic})
122 |
123 |
124 | class Channels(BaseAPI):
125 | def info(self, channel):
126 | return self.get('channels.info', params={'channel': channel})
127 |
128 | def list(self, exclude_archived=None):
129 | return self.get('channels.list',
130 | params={'exclude_archived': exclude_archived})
131 |
132 | def history(self, channel, latest=None, oldest=None, count=None):
133 | return self.get('channels.history',
134 | params={
135 | 'channel': channel,
136 | 'latest': latest,
137 | 'oldest': oldest,
138 | 'count': count
139 | })
140 |
141 | def mark(self, channel, ts):
142 | return self.post('channels.mark',
143 | params={'channel': channel, 'ts': ts})
144 |
145 | def join(self, name):
146 | return self.post('channels.join', params={'name': name})
147 |
148 | def leave(self, channel):
149 | return self.post('channels.leave', params={'channel': channel})
150 |
151 | def invite(self, channel, user):
152 | return self.post('channels.invite',
153 | params={'channel': channel, 'user': user})
154 |
155 | def kick(self, channel, user):
156 | return self.post('channels.kick',
157 | params={'channel': channel, 'user': user})
158 |
159 | def set_purpose(self, channel, purpose):
160 | return self.post('channels.setPurpose',
161 | params={'channel': channel, 'purpose': purpose})
162 |
163 | def set_topic(self, channel, topic):
164 | return self.post('channels.setTopic',
165 | params={'channel': channel, 'topic': topic})
166 |
167 |
168 | class Chat(BaseAPI):
169 | def post_message(self, channel, text, username=None, parse=None,
170 | link_names=None, attachments=None, unfurl_links=None,
171 | icon_url=None, icon_emoji=None):
172 | return self.post('chat.postMessage',
173 | params={
174 | 'channel': channel,
175 | 'text': text,
176 | 'username': username,
177 | 'parse': parse,
178 | 'link_names': link_names,
179 | 'attachments': attachments,
180 | 'unfurl_links': unfurl_links,
181 | 'icon_url': icon_url,
182 | 'icon_emoji': icon_emoji
183 | })
184 |
185 | def update(self, channel, ts, text):
186 | self.post('chat.update',
187 | params={'channel': channel, 'ts': ts, 'text': text})
188 |
189 | def delete(self, channel, ts):
190 | self.post('chat.delete', params={'channel': channel, 'ts': ts})
191 |
192 |
193 | class IM(BaseAPI):
194 | def list(self):
195 | return self.get('im.list')
196 |
197 | def history(self, channel, latest=None, oldest=None, count=None):
198 | return self.get('im.history',
199 | params={
200 | 'channel': channel,
201 | 'latest': latest,
202 | 'oldest': oldest,
203 | 'count': count
204 | })
205 |
206 | def mark(self, channel, ts):
207 | return self.post('im.mark', params={'channel': channel, 'ts': ts})
208 |
209 |
210 | class Search(BaseAPI):
211 | def all(self, query, sort=None, sort_dir=None, highlight=None, count=None,
212 | page=None):
213 | return self.get('search.all',
214 | params={
215 | 'query': query,
216 | 'sort': sort,
217 | 'sort_dir': sort_dir,
218 | 'highlight': highlight,
219 | 'count': count,
220 | 'page': page
221 | })
222 |
223 | def files(self, query, sort=None, sort_dir=None, highlight=None,
224 | count=None, page=None):
225 | return self.get('search.files',
226 | params={
227 | 'query': query,
228 | 'sort': sort,
229 | 'sort_dir': sort_dir,
230 | 'highlight': highlight,
231 | 'count': count,
232 | 'page': page
233 | })
234 |
235 | def messages(self, query, sort=None, sort_dir=None, highlight=None,
236 | count=None, page=None):
237 | return self.get('search.messages',
238 | params={
239 | 'query': query,
240 | 'sort': sort,
241 | 'sort_dir': sort_dir,
242 | 'highlight': highlight,
243 | 'count': count,
244 | 'page': page
245 | })
246 |
247 |
248 | class Files(BaseAPI):
249 | def list(self, user=None, ts_from=None, ts_to=None, types=None,
250 | count=None, page=None):
251 | return self.get('files.list',
252 | params={
253 | 'user': user,
254 | 'ts_from': ts_from,
255 | 'ts_to': ts_to,
256 | 'types': types,
257 | 'count': count,
258 | 'page': page
259 | })
260 |
261 | def info(self, file, count=None, page=None):
262 | return self.get('files.info',
263 | params={'file': file, 'count': count, 'page': page})
264 |
265 | def upload(self, file, filetype=None, filename=None, title=None,
266 | initial_comment=None, channels=None):
267 | with open(file, 'rb') as f:
268 | if isinstance(channels, (tuple, list)):
269 | channels = ','.join(channels)
270 |
271 | return self.post('files.upload',
272 | params={
273 | 'filetype': filetype,
274 | 'filename': filename,
275 | 'title': title,
276 | 'initial_comment': initial_comment,
277 | 'channels': channels
278 | },
279 | files={'file': f})
280 |
281 |
282 | class Stars(BaseAPI):
283 | def list(self, user=None, count=None, page=None):
284 | return self.get('stars.list',
285 | params={'user': user, 'count': count, 'page': page})
286 |
287 |
288 | class Emoji(BaseAPI):
289 | def list(self):
290 | return self.get('emoji.list')
291 |
292 |
293 | class Presence(BaseAPI):
294 | AWAY = 'away'
295 | ACTIVE = 'active'
296 | TYPES = (AWAY, ACTIVE)
297 |
298 | def set(self, presence):
299 | assert presence in Presence.TYPES, 'Invalid presence type'
300 | return self.post('presence.set', params={'presence': presence})
301 |
302 |
303 | class OAuth(BaseAPI):
304 | def access(self, client_id, client_secret, code, redirect_uri=None):
305 | return self.post('oauth.access',
306 | params={
307 | 'client_id': client_id,
308 | 'client_secret': client_secret,
309 | 'code': code,
310 | 'redirect_uri': redirect_uri
311 | })
312 |
313 |
314 | class Slacker(object):
315 | oauth = OAuth()
316 |
317 | def __init__(self, token):
318 | self.im = IM(token=token)
319 | self.api = API(token=token)
320 | self.auth = Auth(token=token)
321 | self.chat = Chat(token=token)
322 | self.users = Users(token=token)
323 | self.files = Files(token=token)
324 | self.stars = Stars(token=token)
325 | self.emoji = Emoji(token=token)
326 | self.search = Search(token=token)
327 | self.groups = Groups(token=token)
328 | self.channels = Channels(token=token)
329 | self.presence = Presence(token=token)
330 |
--------------------------------------------------------------------------------