Compare commits

..

251 Commits

Author SHA1 Message Date
Peter Zignego 0eb9ea31e1 3.1.11 2017-03-19 19:11:16 -04:00
Peter Zignego 8865376c3c Merge branch 'master' of https://github.com/pvzig/SlackKit 2017-03-18 15:21:42 -04:00
Peter Zignego d86d0108ff Update podfile 2017-03-18 15:21:01 -04:00
Peter Zignego b45b7085ce Update Package.swift 2017-03-18 15:17:30 -04:00
Peter Zignego 91193c6b2e Update podspec 2017-03-18 14:07:58 -04:00
Peter Zignego 9720b1c05c Merge branch 'master' of https://github.com/pvzig/SlackKit 2017-03-18 12:20:55 -04:00
Peter Zignego 9766a5ad6c Bump version strings 2017-03-18 12:20:48 -04:00
Peter Zignego 1e181e7580 Update README.md 2017-03-18 12:07:10 -04:00
Peter Zignego f6d6c779ae Update dependencies 2017-03-18 11:14:56 -04:00
Peter Zignego 11bb50f8d2 Delete Podfile.lock 2017-03-13 11:56:05 -04:00
Peter Zignego d721e0ba1a Delete Cartfile.resolved 2017-03-13 11:55:54 -04:00
Peter Zignego ba6066875c Update Package.swift 2017-03-13 11:55:40 -04:00
Peter Zignego 0f1a1a9de5 Update Cartfile 2017-03-13 11:54:43 -04:00
Peter Zignego a81cebf5c8 Update Podfile 2017-03-13 11:54:05 -04:00
Peter Zignego 94c9f7fe5a Bump version 2017-01-25 12:31:34 -05:00
Peter Zignego be8a5879f3 Merge pull request #77 from norwoodsystems/master
Updated podspec
2017-01-25 12:29:03 -05:00
kiancheong c657919704 Updated podspec 2017-01-25 14:42:19 +08:00
Peter Zignego abeef21d7a Merge pull request #75 from sersoft-gmbh/master
Fix author_name in Attachment, add Markdown fields for Attachments, fix optionality
2017-01-21 10:53:28 -05:00
Florian Friedrich 79e3b14593 Add missing ? in AttachmentField initializer 2017-01-20 16:16:35 +01:00
Florian Friedrich d9a4cda9b0 Made AttachmentField init params optional, fix author_name key in Attachment, add markdown fields to attachment 2017-01-20 16:11:08 +01:00
Peter Zignego 12b316da0b Merge pull request #73 from strogonoff/patch-1
OAuth: scopes must be supplied
2017-01-18 09:20:53 -05:00
Anton Strogonoff 13cade5c00 OAuth: scopes must be supplied
Slack API spec requires the "scope" parameter when requesting authorization code. Without it a token cannot be obtained as request fails with an error.
2017-01-18 16:21:03 +07:00
Peter Zignego 051e8dd6c8 Fix podspec 2017-01-16 22:11:52 -05:00
Peter Zignego 1adea1c54e Fix podspec 2017-01-16 22:02:43 -05:00
Peter Zignego aa51cfb2fd Version bump 2017-01-16 21:37:49 -05:00
Peter Zignego 6b8885bd29 Merge pull request #72 from pvzig/3.1.8
3.1.8
2017-01-16 21:34:58 -05:00
Peter Zignego e0fbf1ab4e Add logging of unsupported events 2017-01-16 21:32:21 -05:00
Peter Zignego 366c6e7b77 Merge pull request #68 from pvzig/master-project-structure
Project organization
2017-01-06 17:59:37 -05:00
Peter Zignego 378c116d9b Project organization 2017-01-06 17:42:06 -05:00
Peter Zignego 58bade8ec9 Bump version 2017-01-04 22:10:18 -05:00
Peter Zignego 7d7f5c40d6 Update readme 2017-01-04 22:09:18 -05:00
Peter Zignego c9fd54d106 Merge pull request #66 from pvzig/3.1.7
3.1.7
2017-01-04 22:08:10 -05:00
Peter Zignego 2f1ce799ad URLComponents and WebAPI clean up 2017-01-04 22:06:47 -05:00
Peter Zignego 7f02f4cf99 Update readme 2017-01-03 22:07:57 -05:00
Peter Zignego a9066c0d0a Bump version 2017-01-03 21:29:49 -05:00
Peter Zignego 29739dba74 Merge pull request #65 from pvzig/minor-updates
Code styling
2017-01-03 21:20:00 -05:00
Peter Zignego d60a7094a4 Readme 2017-01-03 21:18:58 -05:00
Peter Zignego 7edd4210f6 Code quality improvements 2017-01-02 22:41:03 -05:00
Peter Zignego 2abaecbd14 Lowercase enums 2017-01-02 22:39:56 -05:00
Peter Zignego 346499e03b Update podspec version 2016-11-25 12:46:48 -05:00
Peter Zignego bc72a52bf5 Update readme 2016-11-24 12:37:48 -05:00
Peter Zignego 0ebd3cb0e7 Merge pull request #60 from pvzig/ios-fix
Fix iOS crash + Swift 3 style changes
2016-11-24 12:19:39 -05:00
Peter Zignego 5d7064ee13 Podfile + versioning 2016-11-24 12:16:53 -05:00
Peter Zignego d02768ddad Bump starscream version 2016-11-24 12:01:54 -05:00
Peter Zignego dbc7b361c8 Merge branch 'master' into ios-fix 2016-11-24 11:59:50 -05:00
Peter Zignego a0de46e3c7 Code quality improvements 2016-11-20 22:10:44 -05:00
Peter Zignego 2d6e19baee Bump starscream version 2016-11-20 22:07:06 -05:00
Peter Zignego 21ec3cb2a1 Fix iOS crash 2016-11-20 22:04:50 -05:00
Peter Zignego cb542da068 Merge pull request #59 from MarcusSmith/master
Fix File Upload Bug
2016-11-18 14:16:18 -05:00
Marcus Smith 788b4e4f7a Remove File data from upload's query parameters, which was preventing a proper URL from being created from the request string 2016-11-18 14:14:02 -05:00
Peter Zignego 24b2292cba Request string bugfix 2016-11-17 16:48:00 -05:00
Peter Zignego 72866262d2 Fix request string bug 2016-11-17 16:30:28 -05:00
Peter Zignego acabbb6c03 Update README.md 2016-11-17 15:04:18 -05:00
Peter Zignego fff0f0befc Merge branch 'master' of https://github.com/pvzig/SlackKit into ios-fix
# Conflicts:
#	SlackKit/Sources/WebAPI.swift
2016-11-17 14:44:35 -05:00
Peter Zignego cf0675dac2 Merge pull request #58 from stucarney/master
Added missing support for chat.meMessage endpoint
2016-11-17 09:08:54 -05:00
Stu Carney 1cffa0ba74 Added missing support for chat.meMessage endpoint 2016-11-16 20:00:22 -06:00
Peter Zignego 9ebd8182a6 Lowercase enums 2016-11-13 17:26:32 -05:00
Peter Zignego 63a8ae7f08 Lowercase enums 2016-11-13 15:34:27 -05:00
Peter Zignego 992f94e870 Lowercase error enums 2016-11-13 15:32:50 -05:00
Peter Zignego 1c476c77ad Lowercase enums 2016-11-11 14:59:43 -05:00
Peter Zignego 28bd612ca0 Carthage search paths 2016-10-11 21:18:34 -04:00
Peter Zignego 45cf2a7ac0 Add back links for carthage 2016-10-11 21:12:31 -04:00
Peter Zignego f7c986c65c Update dependency managers 2016-10-11 21:01:17 -04:00
Peter Zignego ca43df4475 Update project file 2016-10-11 19:43:14 -04:00
Peter Zignego ef71b6abf0 Swifter compatability 2016-10-11 19:41:36 -04:00
Peter Zignego 3ff922a2c0 Project file update 2016-10-11 19:41:28 -04:00
Peter Zignego 646c371bd3 SPM compatability 2016-10-01 12:37:13 -04:00
Peter Zignego 36401009f8 Version bump 2016-09-26 00:05:13 -04:00
Peter Zignego b837554dcf Delete Package.swift until SPM compatability is restored 2016-09-25 23:54:25 -04:00
Peter Zignego 0ba0e019ae Update README.md 2016-09-25 23:52:53 -04:00
Peter Zignego 8771ef3f0a Update readme 2016-09-25 23:51:32 -04:00
Peter Zignego 71e7238483 Merge branch 'swift3'
# Conflicts:
#	README.md
#	SlackKit/Sources/Action.swift
#	SlackKit/Sources/Response.swift
#	SlackKit/Sources/SlackKit.swift
2016-09-25 20:35:45 -04:00
Peter Zignego b75c10fca6 Merge pull request #53 from pvzig/swift3-GM
Swift 3
2016-09-25 20:31:44 -04:00
Peter Zignego 50f85ac103 Carthage 2016-09-20 00:23:26 -04:00
Peter Zignego a7cbb84b34 Update starscream delegate 2016-09-20 00:23:08 -04:00
Peter Zignego ed6789c9a5 Swift 3 update 2016-09-19 23:59:10 -04:00
Peter Zignego ca444930a4 GM 2016-09-14 21:39:32 -04:00
Peter Zignego 21051864ec Add badges 2016-09-13 00:10:55 -04:00
Peter Zignego 36d1553072 Update README.md 2016-09-12 23:42:17 -04:00
Peter Zignego eb14758846 Add badges 2016-09-12 23:40:48 -04:00
Peter Zignego bf3a5c2422 Merge pull request #47 from hiragram/add-missing-scope
Add MissingScope
2016-08-21 22:40:59 -04:00
Yuya 98fd1342e7 Add MissingScope 2016-08-21 13:38:49 +09:00
Peter Zignego 183ed91cb8 Merge branch 'xcode8b4' into swift3 2016-08-03 22:47:16 -04:00
Peter Zignego c241dcf1df Xcode 8 beta 4 2016-08-03 22:47:03 -04:00
Peter Zignego 8007fa6f0f Include style in Action JSON dictionary 2016-08-02 23:24:12 -04:00
Peter Zignego 7ab973a03b Include style in Action JSON dictionary 2016-08-02 23:21:16 -04:00
Peter Zignego 1889ffeeee Fix callback bug when authing via a provided token 2016-07-26 19:11:26 -04:00
Peter Zignego 987be40c91 Fix callback bug when authing via a token 2016-07-26 19:07:53 -04:00
Peter Zignego 2e71ac6876 Bug fix 2016-07-23 11:12:14 -04:00
Peter Zignego df661762a9 Bug fix 2016-07-23 11:09:54 -04:00
Peter Zignego c428cac537 Update readme 2016-07-20 21:44:30 -04:00
Peter Zignego 18c6516df9 Remove Cocoapods 2016-07-20 21:32:57 -04:00
Peter Zignego fe450a7db2 Set swift version 2016-07-20 21:32:41 -04:00
Peter Zignego bb57f59553 Swift 3 support 2016-07-20 18:31:03 -04:00
Peter Zignego e5a4a67bdb Fix imports for SPM 2016-07-18 00:07:03 -04:00
Peter Zignego 1e304f1f6d Update podfile.lock 2016-07-17 23:29:06 -04:00
Peter Zignego 81ebf86abd Update podfile and podspec 2016-07-17 23:04:03 -04:00
Peter Zignego afa16e98b3 Update podspec 2016-07-17 22:50:13 -04:00
Peter Zignego 6596bb2861 SlackKit 2.0.0 2016-07-17 22:20:15 -04:00
Peter Zignego fd12ab0600 Readme updates 2016-07-17 21:49:27 -04:00
Peter Zignego 31508e44dc Merge branch 'feature/message-buttons'
# Conflicts:
#	README.md
#	SlackKit.xcodeproj/project.pbxproj
#	SlackKit/Sources/MessageActionResponder.swift
#	SlackKit/Supporting Files/Info-iOS.plist
#	SlackKit/Supporting Files/Info-tvOS.plist
#	SlackKit/Supporting Files/Info.plist
2016-07-17 21:39:14 -04:00
Peter Zignego b980f371f6 SlackKit 2.0.0 2016-07-17 21:30:56 -04:00
Peter Zignego 9526d739e1 Oauth 2016-07-07 20:22:44 -04:00
Peter Zignego c3af5c33be Increment version 2016-07-04 14:35:51 -04:00
Peter Zignego c6258c57a0 Merge pull request #43 from pvzig/spm-fix
SPM fix
2016-07-04 14:32:31 -04:00
Peter Zignego 45e075aca2 Update readme 2016-07-04 14:30:52 -04:00
Peter Zignego 5ebe40a389 Remove example 2016-07-04 13:15:32 -04:00
Peter Zignego 0a5a8f83f7 Remove example target 2016-07-04 13:14:03 -04:00
Peter Zignego 8f4a287ad0 Fix Package.swift 2016-07-04 13:13:28 -04:00
Peter Zignego deadc8855a Webhook and server 2016-07-04 12:38:06 -04:00
Peter Zignego 98155f00b7 Remove bundled sample 2016-07-02 16:42:36 -04:00
Peter Zignego a55d3be65b Message buttons 2016-06-26 14:39:12 -04:00
Peter Zignego 3b8f1834f0 Code improvements 2016-06-25 18:52:53 -04:00
Peter Zignego 34972eb8af Types get their own .swift files 2016-06-25 18:51:48 -04:00
Peter Zignego f21179f567 Model type organization 2016-06-23 00:13:52 -04:00
Peter Zignego e1bf5a160f Merge pull request #41 from hamin/make-some-channel-setters-public
Make the following Chanel setters public 'unreadCountDisplay', 'unrea…
2016-06-19 22:00:07 -04:00
Haris Amin c66a5fe0db Make the following Chanel setters public 'unreadCountDisplay', 'unread', and 'lastRead' 2016-06-19 17:48:31 -04:00
Peter Zignego f03908bcc7 Merge pull request #38 from natestedman/master
Remove curly quotes from Carthage instructions
2016-06-04 11:13:55 -04:00
Nate Stedman 591b0d9d55 Remove curly quotes from Carthage instructions. 2016-06-04 08:55:23 -04:00
Peter Zignego 6e83fb93d8 Merge pull request #36 from pvzig/feature/additional-footer-fields
1.1.1
2016-06-01 17:22:02 -04:00
Peter Zignego c1d202f433 Increment version 2016-06-01 17:21:23 -04:00
Peter Zignego c7db8ac578 Add new Attachment footer fields
https://api.slack.com/docs/attachments
2016-06-01 17:17:27 -04:00
Peter Zignego d2e430e5bf Merge pull request #35 from pvzig/feature/model-object-improvements
1.1.0
2016-05-22 22:33:12 -04:00
Peter Zignego ebe169bf2a Update podspec 2016-05-22 22:30:29 -04:00
Peter Zignego ca36653dc4 Update readme 2016-05-22 22:30:21 -04:00
Peter Zignego 94895edfac Bump version 2016-05-22 22:27:34 -04:00
Peter Zignego 3ea13ac6c4 Multiple target naming clean up 2016-05-22 22:27:25 -04:00
Peter Zignego e94adfc019 Make timestamp non-optional 2016-05-22 21:59:51 -04:00
Peter Zignego 1291323c5a Remove unused variable 2016-05-22 21:30:41 -04:00
Peter Zignego 4ceb452e6b File reordering 2016-05-22 21:30:27 -04:00
Peter Zignego 457504e786 Code quality 2016-05-22 18:38:00 -04:00
Peter Zignego 6d9a939575 Files only come as an ID via RTM (API change)
https://medium.com/slack-developer-blog/changes-to-file-events-in-the-real-time-messaging-api-5fa75c8c4d99#.1f3k421tz
2016-05-22 18:12:46 -04:00
Peter Zignego efc3847a20 Merge remote-tracking branch 'michallaskowski/remove-unneded-optionals' into feature/model-object-improvements
# Conflicts:
#	SlackKit/Sources/Message.swift
#	SlackKit/Sources/SlackWebAPI.swift
2016-05-21 15:28:28 -04:00
Peter Zignego 3ed139b503 Merge pull request #33 from hamin/fix-message-reactions-parsing
Fixes parsing of Reaction from Message response. It now properly adds…
2016-05-21 12:26:51 -04:00
Peter Zignego a4d9083f72 Merge pull request #34 from muratayusuke/feature/handle_http_error
Hanlde HTTP error
2016-05-21 12:25:18 -04:00
muratayusuke 67b2fa95b3 Hanlde HTTP error 2016-05-21 15:06:57 +09:00
Haris Amin e031f85447 Fixes parsing of Reaction from Message response. It now properly adds users to a parsed Reaction 2016-05-21 01:16:34 -04:00
Peter Zignego 0b1dc6068e Merge remote-tracking branch 'michallaskowski/slack-web-api-without-client' 2016-05-17 20:21:20 -04:00
Peter Zignego b027b5b779 Readme styling 2016-05-15 21:16:09 -04:00
Peter Zignego e507c85ca2 Update readme with note about slow carthage build times 2016-05-15 21:14:43 -04:00
michallaskowski 145dfccfae Remove unneeded Array extension 2016-05-15 23:16:55 +02:00
michallaskowski b4ca2bcc07 Delete optionals from EventDelegate where possible, fix reactionRemoved 2016-05-15 23:16:55 +02:00
michallaskowski 85b2d920ad Gardening Client.swift 2016-05-15 23:16:55 +02:00
michallaskowski 6415a113ed Remove failable initializers 2016-05-15 23:16:55 +02:00
Peter Zignego 3a2324b279 Fix readme 2016-05-15 16:29:40 -04:00
Peter Zignego d647a6b0c9 Update podspec 2016-05-15 16:27:47 -04:00
Peter Zignego 4230d7841c Merge pull request #32 from pvzig/feature/carthage-support
Feature/carthage support
2016-05-15 16:22:18 -04:00
Peter Zignego 8995a54d15 Fix project file and schemes after merge 2016-05-15 16:16:26 -04:00
Peter Zignego ba43a123ea Merge branch 'master' into feature/carthage-support
# Conflicts:
#	SlackKit.podspec
#	SlackKit.xcodeproj/project.pbxproj
#	SlackKit.xcodeproj/xcshareddata/xcschemes/SlackKit.xcscheme
2016-05-15 15:57:27 -04:00
Peter Zignego d00419dc18 Framework build details 2016-05-15 15:41:42 -04:00
Peter Zignego 15f3a30ba9 Framework build details 2016-05-15 15:39:47 -04:00
Peter Zignego befa53e3a9 Update ignore 2016-05-15 15:28:43 -04:00
Peter Zignego 35dfa840e8 Revert "Use submodule to handle core dependency"
This reverts commit edf56e1678.
2016-05-15 15:25:36 -04:00
Peter Zignego edf56e1678 Use submodule to handle core dependency 2016-05-15 15:21:47 -04:00
Peter Zignego 3eab323564 Ignore Cartfile.resolved 2016-05-15 15:04:34 -04:00
Peter Zignego 0b2ee1d10e Clean up project file 2016-05-15 14:51:15 -04:00
michallaskowski 5444948940 Do not hold Client instance in SlackWebAPI 2016-05-15 20:40:05 +02:00
Peter Zignego 13335b5de1 Add additional shared schemes 2016-05-15 14:09:21 -04:00
Peter Zignego c8d8e18db6 Move AttachmentColor enum 2016-05-14 20:11:14 -04:00
Peter Zignego c11321c116 File name comment fixes 2016-05-14 20:08:54 -04:00
Michal Laskowski 0041a118a7 Fix circular dependencies (#30)
* Remove EventDispatcher, EventHandler classes; extend Client

* Make delegates weak

* Remove unneded 'if let delegate = '

* Move files around
2016-05-14 19:51:08 -04:00
Michal Laskowski 6cb5280cf5 Add tvOS target to podspec 2016-05-11 10:12:15 -05:00
Peter Zignego 9cad043957 Add shared scheme 2016-05-06 15:02:39 -04:00
Peter Zignego fa15ed8751 Merge pull request #28 from hamin/add-larger-thumbs-to-file
Adding support for larger size File thumbs
2016-05-04 12:02:42 -04:00
Haris Amin 0c9c146d58 Adding support for larger size File thumbs 2016-05-04 01:58:43 -04:00
Peter Zignego 109a20fac2 Readme update 2016-04-27 23:18:19 -04:00
Peter Zignego a6cf1e3cb6 Revert "Delete and add Cartfile.resolved to ignore"
This reverts commit d3c16bed5f.
2016-04-27 23:17:41 -04:00
Peter Zignego d3c16bed5f Delete and add Cartfile.resolved to ignore 2016-04-27 23:07:55 -04:00
Peter Zignego 797d95ab75 Update readme 2016-04-27 22:54:30 -04:00
Nate Stedman efb7bc9458 Use Carthage for resolving dependencies. 2016-04-27 22:34:28 -04:00
Peter Zignego 79e59f23fb Bump version number 2016-04-27 19:33:48 -04:00
Peter Zignego 01e199659e Update readme 2016-04-27 19:30:48 -04:00
Peter Zignego 851b2f5e14 Merge pull request #27 from pvzig/1.0.2
v1.0.2
2016-04-27 19:26:26 -04:00
Peter Zignego c506512800 Add files.info 2016-04-27 19:18:12 -04:00
Peter Zignego 20fa05605b Merge branch 'master' of https://github.com/pvzig/SlackRTMKit 2016-04-27 17:44:55 -04:00
Peter Zignego 30baf1f76c Add clientConnectionFailed delegate 2016-04-27 17:44:51 -04:00
Peter Zignego 1c3f01f861 Fix typo in example 2016-04-27 17:39:23 -04:00
Peter Zignego 47dc9b3d9f Merge pull request #25 from muratayusuke/feature/make_token_public
Make token public
2016-04-25 10:28:45 -04:00
muratayusuke b9e828ef3d Make token public 2016-04-24 18:44:40 +09:00
Peter Zignego b2be8d0170 Merge pull request #24 from pvzig/1.0.1
v1.0.1
2016-04-09 11:40:17 -04:00
Peter Zignego 45be1b7a3f Bump version number 2016-04-09 11:33:22 -04:00
Peter Zignego 09aa72d43e Update readme to include explanation of Leaderboard example 2016-04-09 11:24:47 -04:00
Peter Zignego bf4b55bbd6 Add leaderboard bot example 2016-04-09 11:02:30 -04:00
Peter Zignego a82279fad1 Code cleanup and improvement 2016-04-04 23:49:25 -04:00
Peter Zignego 500e489d5d Add AttachmentField type and AttachmentColor enum 2016-04-04 21:26:09 -04:00
Peter Zignego 874f4f51e1 Fix send message optional parameter handling 2016-04-03 21:04:49 -04:00
Peter Zignego 654f419f4e Support for rtm.start parameters 2016-04-03 15:45:53 -04:00
Peter Zignego a7c25fe33b Version bump 2016-03-22 21:55:26 -04:00
Peter Zignego d2037f4cc5 Merge pull request #23 from pvzig/feature/client-improvements
Feature/client-improvements
2016-03-22 21:44:20 -04:00
Peter Zignego b87232dfb5 Readme updates 2016-03-22 21:38:17 -04:00
Peter Zignego 9e5678739f Update readme 2016-03-22 21:34:45 -04:00
Peter Zignego 5cc8582d65 Code cleanup 2016-03-22 21:29:16 -04:00
Peter Zignego aa078934c0 File comment web api errors 2016-03-22 20:56:10 -04:00
Peter Zignego 5c157caea3 Client improvements - disconnect, ping-pong, timeout, reconnect 2016-03-22 20:50:29 -04:00
Peter Zignego b567113b5f Update Starscream dependency version 2016-03-22 20:44:58 -04:00
Peter Zignego 5b08fb6031 Merge pull request #21 from pvzig/feature/file-comment
Add support for file comment web API calls
2016-03-19 14:21:52 -04:00
Peter Zignego b48f33fb72 Add support for file comment web API calls 2016-03-19 14:12:04 -04:00
Peter Zignego deccb727a1 Merge pull request #20 from pvzig/feature/reactions-itemUser
Add support for the item user reaction property
2016-03-15 23:48:55 -04:00
Peter Zignego 8f1df8d138 Add support for the itemUser reaction property 2016-03-15 23:48:08 -04:00
Peter Zignego 0048710e24 Merge pull request #19 from pvzig/feature/dnd-read
DND Read Scope Web API implementation
2016-03-15 23:38:08 -04:00
Peter Zignego f6da0ddd32 DND read scope fixes 2016-03-15 23:37:33 -04:00
Peter Zignego 513485e704 DND Read Scope Web API implementation 2016-03-15 23:28:38 -04:00
Peter Zignego fb3719c29d Merge pull request #18 from pvzig/feature/rtm-team-profile
Add support for team profile events
2016-03-15 22:48:43 -04:00
Peter Zignego 76fdc55f9e Team profile events 2016-03-15 22:45:45 -04:00
Peter Zignego 687b57fc1f Version bump 2016-03-01 23:08:24 -05:00
Peter Zignego 319dc9a095 Merge pull request #17 from muratayusuke/bugfix/ignore_int_param
Bugfix: Do not ignore Int and Bool parameters when making requests to the web api
2016-03-01 12:47:20 -05:00
muratayusuke 64440b5c5b Do not ignore Int/Bool param such as count 2016-03-02 02:25:37 +09:00
Peter Zignego 3efe5752cb Merge pull request #16 from pvzig/feature/history-object
Feature/history object
2016-02-28 12:41:22 -05:00
Peter Zignego 31f69e3afd Move History struct to Types.swift, minor changes 2016-02-28 12:40:04 -05:00
muratayusuke 72b29562e5 Introduce history type 2016-02-28 12:29:12 -05:00
Peter Zignego 2b26eb35d7 Merge pull request #14 from pvzig/fix/team-object
fix/team-object
2016-02-27 14:13:53 -05:00
Peter Zignego c4c8e99c0b Bug fixes, team icon support 2016-02-27 14:10:35 -05:00
muratayusuke 5efe222196 Add team icon 2016-02-27 14:06:24 -05:00
Peter Zignego e56ff971b2 Merge pull request #12 from hamin/public-message-initializer
Making Message struct initializer public.
2016-02-27 13:48:42 -05:00
Haris Amin 18b8a31d85 Making Message struct initializer public. 2016-02-27 13:38:52 -05:00
Peter Zignego d386730c75 Project file update 2016-02-21 10:31:13 -05:00
Peter Zignego bc6e94d99a Merge pull request #11 from pvzig/feature/message-attachments
Message attachment support
2016-02-21 10:27:41 -05:00
Peter Zignego 89f5e3786e Message attachment support 2016-02-21 10:18:32 -05:00
Peter Zignego 0665ea0270 Add sample app 2016-02-18 18:21:17 -05:00
Peter Zignego 4ebc33b59a Update podspec, project, info.plist 2016-02-18 13:38:47 -05:00
Peter Zignego 9e93806437 Merge pull request #10 from pvzig/web-api
Add bot accessible Slack Web API endpoints
2016-02-18 13:29:34 -05:00
Peter Zignego 572e363717 Typo fix 2016-02-18 13:27:24 -05:00
Peter Zignego d8c8c3cb57 Updates 2016-02-18 13:24:14 -05:00
Peter Zignego 02eff541b1 Fixes 2016-02-18 12:57:53 -05:00
Peter Zignego 92d6d833ea Clean up 2016-02-15 20:13:10 -05:00
Peter Zignego 78b73c4c99 Bug fixes 2016-02-14 21:31:42 -05:00
Peter Zignego dee376e1b9 Make success and failure closures optional 2016-02-14 21:09:07 -05:00
Peter Zignego 5550658d83 Encode parameters 2016-02-14 21:08:51 -05:00
Peter Zignego 82978bb963 Add additional errors 2016-02-14 20:35:03 -05:00
Peter Zignego 0a203bbc68 Web api clean up 2016-02-14 20:34:41 -05:00
Peter Zignego 38a762c60b File upload 2016-02-14 19:44:05 -05:00
Peter Zignego 96543932af General clean up 2016-02-14 16:35:39 -05:00
Peter Zignego 39fa80904f WebAPI interface clean up 2016-02-14 16:35:19 -05:00
Peter Zignego e4f0429ffb Send message implementation 2016-02-14 14:15:04 -05:00
Peter Zignego ab41c148cd Make Slack message formatting a string extension 2016-02-14 14:14:37 -05:00
Peter Zignego a74aba3c5e Remove depreciated parameters 2016-02-14 14:14:06 -05:00
Peter Zignego 9f266fff64 WebAPI clean up 2016-02-14 12:26:53 -05:00
Peter Zignego 78d31af3f7 Move client code out of SlackWebAPI 2016-02-14 12:04:09 -05:00
Peter Zignego 1a787019ec Add error handling 2016-02-14 11:16:07 -05:00
Peter Zignego f2f25763e7 Move source file to Sources 2016-02-14 10:16:07 -05:00
Peter Zignego de3cf52687 Web API partial implementation
Need to update with error closures across all methods and client integration
2016-02-13 16:04:14 -05:00
Peter Zignego 49b151cd98 Merge branch 'master' into web-api
# Conflicts:
#	SlackKit/Sources/Client.swift
2016-02-11 18:42:11 -05:00
Peter Zignego e9fc66a68f Merge pull request #9 from muratayusuke/feature/multi_clients
Support for multiple client instances
2016-02-11 18:20:42 -05:00
muratayusuke 0e25584191 Work with many Client instances 2016-02-12 02:11:15 +09:00
Peter Zignego 074be89825 Web API scaffolding 2016-02-08 18:38:23 -05:00
Peter Zignego 12dcb791a8 Error handling scaffolding 2016-02-08 18:37:39 -05:00
Peter Zignego de7dbfb9df API interface improvements 2016-02-08 18:37:26 -05:00
Peter Zignego fe49ca7b25 Update podspec 2016-02-02 09:30:40 -05:00
Peter Zignego 02791a4ea0 Move into sources folder 2016-01-18 20:19:33 -05:00
Peter Zignego 64a0b0e8dc Fix networking 2016-01-18 20:11:33 -05:00
Peter Zignego 6653d934f6 Start implementing the Slack Web API 2016-01-18 17:09:31 -05:00
70 changed files with 5520 additions and 1962 deletions
+6 -3
View File
@@ -16,8 +16,11 @@ DerivedData
*.hmap
*.ipa
*.xcuserstate
.build
*.DS_Store
# SwiftPM
Packages/
.build
# CocoaPods
#
@@ -26,10 +29,10 @@ Packages/
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
#
Pods/
SlackKit.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Checkouts
Carthage/Build
+1
View File
@@ -0,0 +1 @@
3.0
+2
View File
@@ -0,0 +1,2 @@
github "https://github.com/daltoniam/Starscream" == 2.0.3
github "https://github.com/httpswift/swifter" == 1.3.3
+2 -25
View File
@@ -1,33 +1,10 @@
//
// Package.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import PackageDescription
let package = Package(
name: "SlackKit",
targets: [],
dependencies: [
.Package(url: "https://github.com/pvzig/Starscream.git",
majorVersion: 1),
.Package(url: "https://github.com/httpswift/swifter", majorVersion: 1),
.Package(url: "https://github.com/daltoniam/Starscream", majorVersion: 2)
]
)
+14 -3
View File
@@ -2,6 +2,17 @@ source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
target 'SlackKit' do
pod 'Starscream'
end
target 'SlackKit OS X' do
pod 'Starscream', '~> 2.0.3'
pod 'Swifter', '~> 1.3.3'
end
target 'SlackKit iOS' do
pod 'Starscream', '~> 2.0.3'
pod 'Swifter', '~> 1.3.3'
end
target 'SlackKit tvOS' do
pod 'Starscream', '~> 2.0.3'
pod 'Swifter', '~> 1.3.3'
end
-10
View File
@@ -1,10 +0,0 @@
PODS:
- Starscream (1.0.2)
DEPENDENCIES:
- Starscream
SPEC CHECKSUMS:
Starscream: 40e2c4c1c770d811f24116b8b7dbeb4542c56767
COCOAPODS: 0.39.0
+284 -124
View File
@@ -1,156 +1,316 @@
![SlackKit](https://cloud.githubusercontent.com/assets/8311605/10260893/5ec60f96-694e-11e5-91fd-da6845942201.png)
##iOS/OS X Slack Client Library
###Description
This is a Slack client library for iOS and OS X written in Swift. It's intended to expose all of the functionality of Slack's [Real Time Messaging API](https://api.slack.com/rtm).
###Installation
####Swift Package Manager (Swift 2.2 and up)
Add SlackKit to your Package.swift
![Swift Version](https://img.shields.io/badge/Swift-3.0.2-orange.svg) ![Plaforms](https://img.shields.io/badge/Platforms-macOS,iOS,tvOS-lightgrey.svg) ![License MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg) [![CocoaPods compatible](https://img.shields.io/badge/CocoaPods-compatible-brightgreen.svg)](https://cocoapods.org) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage) [![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)
## SlackKit: A Swift Slack Client Library
### Description
```swift
import PackageDescription
This is a Slack client library for OS X, iOS, and tvOS written in Swift. It's intended to expose all of the functionality of Slack's [Real Time Messaging API](https://api.slack.com/rtm) as well as the [web APIs](https://api.slack.com/web) that are accessible to [bot users](https://api.slack.com/bot-users). SlackKit also supports Slacks [OAuth 2.0](https://api.slack.com/docs/oauth) flow including the [Add to Slack](https://api.slack.com/docs/slack-button) and [Sign in with Slack](https://api.slack.com/docs/sign-in-with-slack) buttons, [incoming webhooks](https://api.slack.com/incoming-webhooks), [slash commands](https://api.slack.com/slash-commands), and [message buttons](https://api.slack.com/docs/message-buttons).
let package = Package(
dependencies: [
.Package(url: "https://github.com/pvzig/SlackKit.git", majorVersion: 0)
]
)
This is the **Swift 3** branch of SlackKit. SlackKit also has support for [Swift 2.3](https://github.com/pvzig/SlackKit/tree/swift2.3) and [Linux](https://github.com/pvzig/SlackKit/tree/linux).
#### Building the SlackKit Framework
To build the SlackKit project directly, first build the dependencies using Carthage or CocoaPods. To use the framework in your application, install it in one of the following ways:
### Installation
#### CocoaPods
Add SlackKit to your pod file:
```
Run `swift-build` on your applications main directory.
####CocoaPods
Add the pod to your podfile:
```
pod 'SlackKit'
use_frameworks!
pod 'SlackKit', '~> 3.1.11'
```
and run
```
# Use CocoaPods version >= 1.1.0
pod install
```
#### Carthage
Add SlackKit to your Cartfile:
```
github "https://github.com/pvzig/slackkit.git"
```
and run
```
carthage bootstrap
```
Drag the built `SlackKit.framework` into your Xcode project.
#### Swift Package Manager
Add SlackKit to your Package.swift
```swift
import PackageDescription
let package = Package(
dependencies: [
.Package(url: "https://github.com/pvzig/SlackKit.git", majorVersion: 3)
]
)
```
Run `swift build` on your applications main directory.
To use the library in your project import it:
```
import SlackKit
```
###Usage
To use SlackKit you'll need a bearer token which identifies a single user. You can generate a [full access token or create one using OAuth 2](https://api.slack.com/web).
### Usage
Once you have a token, give it to the Client:
#### OAuth
Slack has [many different oauth scopes](https://api.slack.com/docs/oauth-scopes) that can be combined in different ways. If your application does not request the proper OAuth scopes, your API calls will fail.
If you authenticate using OAuth and the Add to Slack or Sign in with Slack buttons this is handled for you.
If you wish to make OAuth requests yourself, you can generate them using the `authorizeRequest` function on `SlackKit`s `oauth` property:
```swift
Client.sharedInstance.setAuthToken("YOUR_SLACK_AUTH_TOKEN")
func authorizeRequest(scope:[Scope], redirectURI: String, state: String = "slackkit", team: String? = nil)
```
and connect:
For local development of things like OAuth, slash commands, and message buttons that require connecting over `https`, you may want to use a tool like [ngrok](https://ngrok.com) or [localtunnel](http://localtunnel.me).
#### Incoming Webhooks
After [configuring your incoming webhook in Slack](https://my.slack.com/services/new/incoming-webhook/), initialize IncomingWebhook with the provided URL and use `postMessage` to send messages.
```swift
Client.sharedInstance.connect()
let incoming = IncomingWebhook(url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX")
let message = Response(text: "Hello, World!")
incoming.postMessage(message)
```
#### Slash Commands
After [configuring your slash command in Slack](https://my.slack.com/services/new/slash-commands) (you can also provide slash commands as part of a [Slack App](https://api.slack.com/slack-apps)), initialize a webhook server with the token for the slash command, a configured route, and a response.
```swift
let response = Response(text: "Hello, World!", responseType: .inChannel)
let webhook = WebhookServer(token: "SLASH-COMMAND-TOKEN", route: "hello_world", response: response)
webhook.start()
```
When a user enters that slash command, it will hit your configured route and return the response you specified.
To add additional routes and responses, you can use WebhookServers addRoute function:
```swift
func addRoute(route: String, response: Response)
```
#### Message Buttons
If you are developing a Slack App and are authorizing using OAuth, you can use [message buttons](https://api.slack.com/docs/message-buttons).
To send messages with actions, add them to an attachment:
```swift
let helloAction = Action(name: "hello_world", text: "Hello, World!")
let attachment = Attachment(fallback: "Hello World Attachment", title: "Attachment with an Action Button", callbackID: "helloworld", actions: [helloAction])
```
To act on message actions, initialize an instance of the `MessageActionServer` using your apps verification token, your specified interactive messages request URL route, and a `MessageActionResponder`:
```swift
let action = Action(name: "hello_world", text: "Hello, World!")
let response = Response(text: "Hello, 🌎!", responseType: .inChannel)
let responder = MessageActionResponder(responses: [(action, response)])
let server = MessageActionServer(token: "SLACK-APP-VERIFICATION-TOKEN", route: "actions", responder: responder)
server.start()
```
#### Bot Users
To deploy a bot user using SlackKit you'll need a bearer token which identifies a single user. You can generate a [full access token or create one using OAuth 2](https://api.slack.com/web).
Initialize a SlackKit instance using your [applications Client ID and Client Secret](https://api.slack.com/apps) to set up SlackKit for OAuth authorization:
```swift
let bot = SlackKit(clientID: "CLIENT_ID", clientSecret: "CLIENT_SECRET")
```
or use a manually acquired token:
```swift
let bot = SlackKit(withAPIToken: "xoxp-YOUR-SLACK-API-TOKEN")
```
#### Client Connection Options
You can also set options for a ping/pong interval, timeout interval, and automatic reconnection:
```swift
let options = ClientOptions(pingInterval: 2, timeout: 10, reconnect: false)
let bot = SlackKit(clientID: "CLIENT_ID", clientSecret: "CLIENT_SECRET", clientOptions: options)
```
Once connected, the client will begin to consume any messages sent by the Slack RTM API.
####Delegate methods
#### Web API Methods
SlackKit currently supports the a subset of the Slack Web APIs that are available to bot users:
- api.test
- auth.revoke
- auth.test
- channels.history
- channels.info
- channels.list
- channels.mark
- channels.setPurpose
- channels.setTopic
- chat.delete
- chat.meMessage
- chat.postMessage
- chat.update
- emoji.list
- files.comments.add
- files.comments.edit
- files.comments.delete
- files.delete
- files.info
- files.upload
- groups.close
- groups.history
- groups.info
- groups.list
- groups.mark
- groups.open
- groups.setPurpose
- groups.setTopic
- im.close
- im.history
- im.list
- im.mark
- im.open
- mpim.close
- mpim.history
- mpim.list
- mpim.mark
- mpim.open
- oauth.access
- pins.add
- pins.list
- pins.remove
- reactions.add
- reactions.get
- reactions.list
- reactions.remove
- rtm.start
- stars.add
- stars.remove
- team.info
- users.getPresence
- users.info
- users.list
- users.setActive
- users.setPresence
They can be accessed through a Client objects `webAPI` property:
```swift
client.webAPI.authenticationTest({(auth) in
print(auth)
}, failure: {(error) in
print(error)
})
```
#### Delegate methods
To receive delegate callbacks for events, register an object as the delegate for those events using the `onClientInitalization` block:
```swift
let bot = SlackKit(clientID: clientID, clientSecret: clientSecret)
bot.onClientInitalization = { (client: Client) in
DispatchQueue.main.async(execute: {
client.messageEventsDelegate = self
})
}
```
Delegate callbacks contain a reference to the Client where the event occurred.
There are a number of delegates that you can set to receive callbacks for certain events.
#####SlackEventsDelegate
##### ConnectionEventsDelegate
```swift
func clientConnected()
func clientDisconnected()
func preferenceChanged(preference: String, value: AnyObject)
func userChanged(user: User)
func presenceChanged(user: User?, presence: String?)
func manualPresenceChanged(user: User?, presence: String?)
func botEvent(bot: Bot)
connected(_ client: Client)
disconnected(_ client: Client)
connectionFailed(_ client: Client, error: SlackError)
```
##### MessageEventsDelegate
```swift
sent(_ message: Message, client: Client)
received(_ message: Message, client: Client)
changed(_ message: Message, client: Client)
deleted(_ message: Message?, client: Client)
```
##### ChannelEventsDelegate
```swift
userTypingIn(_ channel: Channel, user: User, client: Client)
marked(_ channel: Channel, timestamp: String, client: Client)
created(_ channel: Channel, client: Client)
deleted(_ channel: Channel, client: Client)
renamed(_ channel: Channel, client: Client)
archived(_ channel: Channel, client: Client)
historyChanged(_ channel: Channel, client: Client)
joined(_ channel: Channel, client: Client)
left(_ channel: Channel, client: Client)
```
##### DoNotDisturbEventsDelegate
```swift
updated(_ status: DoNotDisturbStatus, client: Client)
userUpdated(_ status: DoNotDisturbStatus, user: User, client: Client)
```
##### GroupEventsDelegate
```swift
opened(_ group: Channel, client: Client)
```
##### FileEventsDelegate
```swift
processed(_ file: File, client: Client)
madePrivate(_ file: File, client: Client)
deleted(_ file: File, client: Client)
commentAdded(_ file: File, comment: Comment, client: Client)
commentEdited(_ file: File, comment: Comment, client: Client)
commentDeleted(_ file: File, comment: Comment, client: Client)
```
##### PinEventsDelegate
```swift
pinned(_ item: Item, channel: Channel?, client: Client)
unpinned(_ item: Item, channel: Channel?, client: Client)
```
##### StarEventsDelegate
```swift
starred(_ item: Item, starred: Bool, _ client: Client)
```
##### ReactionEventsDelegate
```swift
added(_ reaction: String, item: Item, itemUser: String, client: Client)
removed(_ reaction: String, item: Item, itemUser: String, client: Client)
```
##### SlackEventsDelegate
```swift
preferenceChanged(_ preference: String, value: Any?, client: Client)
userChanged(_ user: User, client: Client)
presenceChanged(_ user: User, presence: String, client: Client)
manualPresenceChanged(_ user: User, presence: String, client: Client)
botEvent(_ bot: Bot, client: Client)
```
##### TeamEventsDelegate
```swift
userJoined(_ user: User, client: Client)
planChanged(_ plan: String, client: Client)
preferencesChanged(_ preference: String, value: Any?, client: Client)
nameChanged(_ name: String, client: Client)
domainChanged(_ domain: String, client: Client)
emailDomainChanged(_ domain: String, client: Client)
emojiChanged(_ client: Client)
```
##### SubteamEventsDelegate
```swift
event(_ userGroup: UserGroup, client: Client)
selfAdded(_ subteamID: String, client: Client)
selfRemoved(_ subteamID: String, client: Client)
```
##### TeamProfileEventsDelegate
```swift
changed(_ profile: CustomProfile, client: Client)
deleted(_ profile: CustomProfile, client: Client)
reordered(_ profile: CustomProfile, client: Client)
```
#####MessageEventsDelegate
```swift
func messageSent(message: Message)
func messageReceived(message: Message)
func messageChanged(message: Message)
func messageDeleted(message: Message?)
```
### Examples
[Check out example applications here.](https://github.com/pvzig/SlackKit-examples)
#####ChannelEventsDelegate
```swift
func userTyping(channel: Channel?, user: User?)
func channelMarked(channel: Channel, timestamp: String?)
func channelCreated(channel: Channel)
func channelDeleted(channel: Channel)
func channelRenamed(channel: Channel)
func channelArchived(channel: Channel)
func channelHistoryChanged(channel: Channel)
func channelJoined(channel: Channel)
func channelLeft(channel: Channel)
```
#####DoNotDisturbEventsDelegate
```swift
doNotDisturbUpdated(dndStatus: DoNotDisturbStatus)
doNotDisturbUserUpdated(dndStatus: DoNotDisturbStatus, user: User?)
```
#####GroupEventsDelegate
```swift
func groupOpened(group: Channel)
```
#####FileEventsDelegate
```swift
func fileProcessed(file: File)
func fileMadePrivate(file: File)
func fileDeleted(file: File)
func fileCommentAdded(file: File, comment: Comment)
func fileCommentEdited(file: File, comment: Comment)
func fileCommentDeleted(file: File, comment: Comment)
```
#####PinEventsDelegate
```swift
func itemPinned(item: Item?, channel: Channel?)
func itemUnpinned(item: Item?, channel: Channel?)
```
#####StarEventsDelegate
```swift
func itemStarred(item: Item, star: Bool)
```
#####ReactionEventsDelegate
```swift
func reactionAdded(reaction: String?, item: Item?)
func reactionRemoved(reaction: String?, item: Item?)
```
#####TeamEventsDelegate
```swift
func teamJoined(user: User)
func teamPlanChanged(plan: String)
func teamPreferencesChanged(preference: String, value: AnyObject)
func teamNameChanged(name: String)
func teamDomainChanged(domain: String)
func teamEmailDomainChanged(domain: String)
func teamEmojiChanged()
```
#####SubteamEventsDelegate
```swift
func subteamEvent(userGroup: UserGroup)
func subteamSelfAdded(subteamID: String)
func subteamSelfRemoved(subteamID: String)
```
###Examples
####Sending a Message:
```swift
Client.sharedInstance.sendMessage(message: "Hello, world!", channelID: "CHANNEL_ID")
```
####Print a List of Users in a Channel:
```swift
let users = Client.sharedInstance.channels?["CHANNEL_ID"]?.members
print(users)
```
###Get In Touch
### Get In Touch
[@pvzig](https://twitter.com/pvzig)
<peter@launchsoft.co>
+7 -5
View File
@@ -1,16 +1,18 @@
Pod::Spec.new do |s|
s.name = "SlackKit"
s.version = "0.9.6"
s.summary = "a Slack client library for iOS and OS X written in Swift"
s.version = "3.1.10"
s.summary = "a Slack client library for OS X, iOS, and tvOS written in Swift"
s.homepage = "https://github.com/pvzig/SlackKit"
s.license = 'MIT'
s.author = { "Peter Zignego" => "peter@launchsoft.co" }
s.source = { :git => "https://github.com/pvzig/SlackKit.git", :tag => s.version.to_s }
s.social_media_url = 'https://twitter.com/pvzig'
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.9'
s.osx.deployment_target = '10.10'
s.tvos.deployment_target = '9.0'
s.requires_arc = true
s.source_files = 'SlackKit/Sources/*.swift'
s.source_files = 'Sources/SlackKit/**/*.swift'
s.frameworks = 'Foundation'
s.dependency 'Starscream', '~> 1.0.2'
s.dependency 'Starscream'
s.dependency 'Swifter'
end
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:SlackRTMKit.xcodeproj">
location = "self:SlackKit.xcodeproj">
</FileRef>
</Workspace>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "26072A331BB48B3A00CD650C"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit OS X"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "26072A331BB48B3A00CD650C"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit OS X"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "26072A331BB48B3A00CD650C"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit OS X"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "263993951CE90EE0004A6E93"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit iOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "263993951CE90EE0004A6E93"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit iOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "263993951CE90EE0004A6E93"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit iOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "263993B41CE90EED004A6E93"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit tvOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "263993B41CE90EED004A6E93"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit tvOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "263993B41CE90EED004A6E93"
BuildableName = "SlackKit.framework"
BlueprintName = "SlackKit tvOS"
ReferencedContainer = "container:SlackKit.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:SlackKit.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
-246
View File
@@ -1,246 +0,0 @@
//
// Client.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import Starscream
public class Client: WebSocketDelegate {
internal(set) public var connected = false
internal(set) public var authenticated = false
internal(set) public var authenticatedUser: User?
internal(set) public var team: Team?
internal(set) public var channels = [String: Channel]()
internal(set) public var users = [String: User]()
internal(set) public var userGroups = [String: UserGroup]()
internal(set) public var bots = [String: Bot]()
internal(set) public var files = [String: File]()
internal(set) public var sentMessages = [String: Message]()
//MARK: - Delegates
public var slackEventsDelegate: SlackEventsDelegate?
public var messageEventsDelegate: MessageEventsDelegate?
public var doNotDisturbEventsDelegate: DoNotDisturbEventsDelegate?
public var channelEventsDelegate: ChannelEventsDelegate?
public var groupEventsDelegate: GroupEventsDelegate?
public var fileEventsDelegate: FileEventsDelegate?
public var pinEventsDelegate: PinEventsDelegate?
public var starEventsDelegate: StarEventsDelegate?
public var reactionEventsDelegate: ReactionEventsDelegate?
public var teamEventsDelegate: TeamEventsDelegate?
public var subteamEventsDelegate: SubteamEventsDelegate?
private var token = "SLACK_AUTH_TOKEN"
public func setAuthToken(token: String) {
self.token = token
}
private var webSocket: WebSocket?
required public init() {
}
public static let sharedInstance = Client()
//MARK: - Connection
public func connect() {
let request = NSURLRequest(URL: NSURL(string:"https://slack.com/api/rtm.start?token="+token)!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.currentQueue()!) {
(response, data, error) -> Void in
guard let data = data else {
return
}
do {
let result = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject]
if (result["ok"] as! Bool == true) {
self.initialSetup(result)
let socketURL = NSURL(string: result["url"] as! String)
self.webSocket = WebSocket(url: socketURL!)
self.webSocket?.delegate = self
self.webSocket?.connect()
}
} catch _ {
print(error)
}
}
}
//MARK: - Message send
public func sendMessage(message: String, channelID: String) {
if (connected) {
if let data = formatMessageToSlackJsonString(msg: message, channel: channelID) {
let string = NSString(data: data, encoding: NSUTF8StringEncoding)
self.webSocket?.writeString(string as! String)
}
}
}
private func formatMessageToSlackJsonString(message: (msg: String, channel: String)) -> NSData? {
let json: [String: AnyObject] = [
"id": NSDate().timeIntervalSince1970,
"type": "message",
"channel": message.channel,
"text": slackFormatEscaping(message.msg)
]
addSentMessage(json)
do {
let data = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions.PrettyPrinted)
return data
}
catch _ {
return nil
}
}
private func addSentMessage(dictionary: [String: AnyObject]) {
var message = dictionary
let ts = message["id"] as? NSNumber
message.removeValueForKey("id")
message["ts"] = ts?.stringValue
message["user"] = self.authenticatedUser?.id
sentMessages[ts!.stringValue] = Message(message: message)
}
private func slackFormatEscaping(string: String) -> String {
var escapedString = string.stringByReplacingOccurrencesOfString("&", withString: "&amp;")
escapedString = escapedString.stringByReplacingOccurrencesOfString("<", withString: "&lt;")
escapedString = escapedString.stringByReplacingOccurrencesOfString(">", withString: "&gt;")
return escapedString
}
//MARK: - Client setup
private func initialSetup(json: [String: AnyObject]) {
team = Team(team: json["team"] as? [String: AnyObject])
authenticatedUser = User(user: json["self"] as? [String: AnyObject])
authenticatedUser?.doNotDisturbStatus = DoNotDisturbStatus(status: json["dnd"] as? [String: AnyObject])
enumerateUsers(json["users"] as? Array)
enumerateChannels(json["channels"] as? Array)
enumerateGroups(json["groups"] as? Array)
enumerateMPIMs(json["mpims"] as? Array)
enumerateIMs(json["ims"] as? Array)
enumerateBots(json["bots"] as? Array)
enumerateSubteams(json["subteams"] as? [String: AnyObject])
}
private func enumerateUsers(users: [AnyObject]?) {
if let users = users {
for user in users {
let u = User(user: user as? [String: AnyObject])
self.users[u!.id!] = u
}
}
}
private func enumerateChannels(channels: [AnyObject]?) {
if let channels = channels {
for channel in channels {
let c = Channel(channel: channel as? [String: AnyObject])
self.channels[c!.id!] = c
}
}
}
private func enumerateGroups(groups: [AnyObject]?) {
if let groups = groups {
for group in groups {
let g = Channel(channel: group as? [String: AnyObject])
self.channels[g!.id!] = g
}
}
}
private func enumerateIMs(ims: [AnyObject]?) {
if let ims = ims {
for im in ims {
let i = Channel(channel: im as? [String: AnyObject])
self.channels[i!.id!] = i
}
}
}
private func enumerateMPIMs(mpims: [AnyObject]?) {
if let mpims = mpims {
for mpim in mpims {
let m = Channel(channel: mpim as? [String: AnyObject])
self.channels[m!.id!] = m
}
}
}
private func enumerateBots(bots: [AnyObject]?) {
if let bots = bots {
for bot in bots {
let b = Bot(bot: bot as? [String: AnyObject])
self.bots[b!.id!] = b
}
}
}
private func enumerateSubteams(subteams: [String: AnyObject]?) {
if let subteams = subteams {
if let all = subteams["all"] as? [[String: AnyObject]] {
for item in all {
let u = UserGroup(userGroup: item)
self.userGroups[u!.id!] = u
}
}
if let auth = subteams["self"] as? [String] {
for item in auth {
authenticatedUser?.userGroups = [String: String]()
authenticatedUser?.userGroups![item] = item
}
}
}
}
// MARK: - WebSocketDelegate
public func websocketDidConnect(socket: WebSocket) {
}
public func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
connected = false
authenticated = false
webSocket = nil
if let delegate = slackEventsDelegate {
delegate.clientDisconnected()
}
}
public func websocketDidReceiveMessage(socket: WebSocket, text: String) {
guard let data = text.dataUsingEncoding(NSUTF8StringEncoding) else {
return
}
do {
try EventDispatcher.eventDispatcher(NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments) as! [String: AnyObject])
}
catch _ {
}
}
public func websocketDidReceiveData(socket: WebSocket, data: NSData) {
}
}
-218
View File
@@ -1,218 +0,0 @@
//
// Message.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal enum EventType: String {
case Hello = "hello"
case Message = "message"
case UserTyping = "user_typing"
case ChannelMarked = "channel_marked"
case ChannelCreated = "channel_created"
case ChannelJoined = "channel_joined"
case ChannelLeft = "channel_left"
case ChannelDeleted = "channel_deleted"
case ChannelRenamed = "channel_rename"
case ChannelArchive = "channel_archive"
case ChannelUnarchive = "channel_unarchive"
case ChannelHistoryChanged = "channel_history_changed"
case DNDUpdated = "dnd_updated"
case DNDUpatedUser = "dnd_updated_user"
case IMCreated = "im_created"
case IMOpen = "im_open"
case IMClose = "im_close"
case IMMarked = "im_marked"
case IMHistoryChanged = "im_history_changed"
case GroupJoined = "group_joined"
case GroupLeft = "group_left"
case GroupOpen = "group_open"
case GroupClose = "group_close"
case GroupArchive = "group_archive"
case GroupUnarchive = "group_unarchive"
case GroupRename = "group_rename"
case GroupMarked = "group_marked"
case GroupHistoryChanged = "group_history_changed"
case FileCreated = "file_created"
case FileShared = "file_shared"
case FileUnshared = "file_unshared"
case FilePublic = "file_public"
case FilePrivate = "file_private"
case FileChanged = "file_change"
case FileDeleted = "file_deleted"
case FileCommentAdded = "file_comment_added"
case FileCommentEdited = "file_comment_edited"
case FileCommentDeleted = "file_comment_deleted"
case PinAdded = "pin_added"
case PinRemoved = "pin_removed"
case PresenceChange = "presence_change"
case ManualPresenceChange = "manual_presence_change"
case PrefChange = "pref_change"
case UserChange = "user_change"
case TeamJoin = "team_join"
case StarAdded = "star_added"
case StarRemoved = "star_removed"
case ReactionAdded = "reaction_added"
case ReactionRemoved = "reaction_removed"
case EmojiChanged = "emoji_changed"
case CommandsChanged = "commands_changed"
case TeamPlanChange = "team_plan_change"
case TeamPrefChange = "team_pref_change"
case TeamRename = "team_rename"
case TeamDomainChange = "team_domain_change"
case EmailDomainChange = "email_domain_change"
case BotAdded = "bot_added"
case BotChanged = "bot_changed"
case AccountsChanged = "accounts_changed"
case TeamMigrationStarted = "team_migration_started"
case SubteamCreated = "subteam_created"
case SubteamUpdated = "subteam_updated"
case SubteamSelfAdded = "subteam_self_added"
case SubteamSelfRemoved = "subteam_self_removed"
case Ok = "ok"
case Error = "error"
}
internal enum MessageSubtype: String {
case BotMessage = "bot_message"
case MeMessage = "me_message"
case MessageChanged = "message_changed"
case MessageDeleted = "message_deleted"
case ChannelJoin = "channel_join"
case ChannelLeave = "channel_leave"
case ChannelTopic = "channel_topic"
case ChannelPurpose = "channel_purpose"
case ChannelName = "channel_name"
case ChannelArchive = "channel_archive"
case ChannelUnarchive = "channel_unarchive"
case GroupJoin = "group_join"
case GroupLeave = "group_leave"
case GroupTopic = "group_topic"
case GroupPurpose = "group_purpose"
case GroupName = "group_name"
case GroupArchive = "group_archive"
case GroupUnarchive = "group_unarchive"
case FileShare = "file_share"
case FileComment = "file_comment"
case FileMention = "file_mention"
case PinnedItem = "pinned_item"
case UnpinnedItem = "unpinned_item"
}
internal struct Event {
let type: EventType?
let ts: String?
let subtype: String?
let channelID: String?
let text: String?
let eventTs: String?
let latest: String?
let hidden: Bool?
let isStarred: Bool?
let hasPins: Bool?
let pinnedTo: [String]?
let fileID: String?
let presence: String?
let name: String?
let value: AnyObject?
let plan: String?
let url: String?
let domain: String?
let emailDomain: String?
let reaction: String?
let replyTo: Double?
let reactions: [[String: AnyObject]]?
let edited: Edited?
let bot: Bot?
let channel: Channel?
let comment: Comment?
let user: User?
let file: File?
let message: Message?
let nestedMessage: Message?
let item: Item?
let dndStatus: DoNotDisturbStatus?
let subteam: UserGroup?
let subteamID: String?
init(event:[String: AnyObject]) {
if let eventType = event["type"] as? String {
type = EventType(rawValue:eventType)
} else {
type = EventType(rawValue: "ok")
}
ts = event["ts"] as? String
subtype = event["subtype"] as? String
channelID = event["channel_id"] as? String
text = event["text"] as? String
eventTs = event["event_ts"] as? String
latest = event["latest"] as? String
hidden = event["hidden"] as? Bool
isStarred = event["is_starred"] as? Bool
hasPins = event["has_pins"] as? Bool
pinnedTo = event["pinned_top"] as? [String]
fileID = event["file_id"] as? String
presence = event["presence"] as? String
name = event["name"] as? String
value = event["value"]
plan = event["plan"] as? String
url = event["url"] as? String
domain = event["domain"] as? String
emailDomain = event["email_domain"] as? String
reaction = event["reaction"] as? String
replyTo = event["reply_to"] as? Double
reactions = event["reactions"] as? [[String: AnyObject]]
bot = Bot(bot: event["bot"] as? [String: AnyObject])
edited = Edited(edited:event["edited"] as? [String: AnyObject])
dndStatus = DoNotDisturbStatus(status: event["dnd_status"] as? [String: AnyObject])
item = Item(item: event["item"] as? [String: AnyObject])
subteam = UserGroup(userGroup: event["subteam"] as? [String: AnyObject])
subteamID = event["subteam_id"] as? String
message = Message(message: event)
nestedMessage = Message(message: event["message"] as? [String: AnyObject])
// Comment, Channel, User, and File can come across as Strings or Dictionaries
if (Comment(comment: event["comment"] as? [String: AnyObject])?.id == nil) {
comment = Comment(id: event["comment"] as? String)
} else {
comment = Comment(comment: event["comment"] as? [String: AnyObject])
}
if (User(user: event["user"] as? [String: AnyObject])?.id == nil) {
user = User(id: event["user"] as? String)
} else {
user = User(user: event["user"] as? [String: AnyObject])
}
if (File(file: event["file"] as? [String: AnyObject])?.id == nil) {
file = File(id: event["file"] as? String)
} else {
file = File(file: event["file"] as? [String: AnyObject])
}
if (Channel(channel: event["channel"] as? [String: AnyObject])?.id == nil) {
channel = Channel(id: event["channel"] as? String)
} else {
channel = Channel(channel: event["channel"] as? [String: AnyObject])
}
}
}
-101
View File
@@ -1,101 +0,0 @@
//
// EventDelegate.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
public protocol SlackEventsDelegate {
func clientConnected()
func clientDisconnected()
func preferenceChanged(preference: String, value: AnyObject)
func userChanged(user: User)
func presenceChanged(user: User?, presence: String?)
func manualPresenceChanged(user: User?, presence: String?)
func botEvent(bot: Bot)
}
public protocol MessageEventsDelegate {
func messageSent(message: Message)
func messageReceived(message: Message)
func messageChanged(message: Message)
func messageDeleted(message: Message?)
}
public protocol ChannelEventsDelegate {
func userTyping(channel: Channel?, user: User?)
func channelMarked(channel: Channel, timestamp: String?)
func channelCreated(channel: Channel)
func channelDeleted(channel: Channel)
func channelRenamed(channel: Channel)
func channelArchived(channel: Channel)
func channelHistoryChanged(channel: Channel)
func channelJoined(channel: Channel)
func channelLeft(channel: Channel)
}
public protocol DoNotDisturbEventsDelegate {
func doNotDisturbUpdated(dndStatus: DoNotDisturbStatus)
func doNotDisturbUserUpdated(dndStatus: DoNotDisturbStatus, user: User?)
}
public protocol GroupEventsDelegate {
func groupOpened(group: Channel)
}
public protocol FileEventsDelegate {
func fileProcessed(file: File)
func fileMadePrivate(file: File)
func fileDeleted(file: File)
func fileCommentAdded(file: File, comment: Comment)
func fileCommentEdited(file: File, comment: Comment)
func fileCommentDeleted(file: File, comment: Comment)
}
public protocol PinEventsDelegate {
func itemPinned(item: Item?, channel: Channel?)
func itemUnpinned(item: Item?, channel: Channel?)
}
public protocol StarEventsDelegate {
func itemStarred(item: Item, star: Bool)
}
public protocol ReactionEventsDelegate {
func reactionAdded(reaction: String?, item: Item?)
func reactionRemoved(reaction: String?, item: Item?)
}
public protocol TeamEventsDelegate {
func teamJoined(user: User)
func teamPlanChanged(plan: String)
func teamPreferencesChanged(preference: String, value: AnyObject)
func teamNameChanged(name: String)
func teamDomainChanged(domain: String)
func teamEmailDomainChanged(domain: String)
func teamEmojiChanged()
}
public protocol SubteamEventsDelegate {
func subteamEvent(userGroup: UserGroup)
func subteamSelfAdded(subteamID: String)
func subteamSelfRemoved(subteamID: String)
}
-159
View File
@@ -1,159 +0,0 @@
//
// EventDispatcher.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal struct EventDispatcher {
static func eventDispatcher(event: [String: AnyObject]) {
let event = Event(event: event)
if let type = event.type {
switch type {
case .Hello:
EventHandler.connected()
case .Ok:
EventHandler.messageSent(event)
case .Message:
if (event.subtype != nil) {
messageDispatcher(event)
} else {
EventHandler.messageReceived(event)
}
case .UserTyping:
EventHandler.userTyping(event)
case .ChannelMarked, .IMMarked, .GroupMarked:
EventHandler.channelMarked(event)
case .ChannelCreated, .IMCreated:
EventHandler.channelCreated(event)
case .ChannelJoined, .GroupJoined:
EventHandler.channelJoined(event)
case .ChannelLeft, .GroupLeft:
EventHandler.channelLeft(event)
case .ChannelDeleted:
EventHandler.channelDeleted(event)
case .ChannelRenamed, .GroupRename:
EventHandler.channelRenamed(event)
case .ChannelArchive, .GroupArchive:
EventHandler.channelArchived(event, archived: true)
case .ChannelUnarchive, .GroupUnarchive:
EventHandler.channelArchived(event, archived: false)
case .ChannelHistoryChanged, .IMHistoryChanged, .GroupHistoryChanged:
EventHandler.channelHistoryChanged(event)
case .DNDUpdated:
EventHandler.doNotDisturbUpdated(event)
case .DNDUpatedUser:
EventHandler.doNotDisturbUserUpdated(event)
case .IMOpen, .GroupOpen:
EventHandler.open(event, open: true)
case .IMClose, .GroupClose:
EventHandler.open(event, open: false)
case .FileCreated:
EventHandler.processFile(event)
case .FileShared:
EventHandler.processFile(event)
case .FileUnshared:
EventHandler.processFile(event)
case .FilePublic:
EventHandler.processFile(event)
case .FilePrivate:
EventHandler.filePrivate(event)
case .FileChanged:
EventHandler.processFile(event)
case .FileDeleted:
EventHandler.deleteFile(event)
case .FileCommentAdded:
EventHandler.fileCommentAdded(event)
case .FileCommentEdited:
EventHandler.fileCommentEdited(event)
case .FileCommentDeleted:
EventHandler.fileCommentDeleted(event)
case .PinAdded:
EventHandler.pinAdded(event)
case .PinRemoved:
EventHandler.pinRemoved(event)
case .PresenceChange:
EventHandler.presenceChange(event)
case .ManualPresenceChange:
EventHandler.manualPresenceChange(event)
case .PrefChange:
EventHandler.changePreference(event)
case .UserChange:
EventHandler.userChange(event)
case .TeamJoin:
EventHandler.teamJoin(event)
case .StarAdded:
EventHandler.itemStarred(event, star: true)
case .StarRemoved:
EventHandler.itemStarred(event, star: false)
case .ReactionAdded:
EventHandler.addedReaction(event)
case .ReactionRemoved:
EventHandler.removedReaction(event)
case .EmojiChanged:
EventHandler.emojiChanged(event)
case .CommandsChanged:
// Not implemented per Slack documentation.
break
case .TeamPlanChange:
EventHandler.teamPlanChange(event)
case .TeamPrefChange:
EventHandler.teamPreferenceChange(event)
case .TeamRename:
EventHandler.teamNameChange(event)
case .TeamDomainChange:
EventHandler.teamDomainChange(event)
case .EmailDomainChange:
EventHandler.emailDomainChange(event)
case .BotAdded:
EventHandler.bot(event)
case .BotChanged:
EventHandler.bot(event)
case .AccountsChanged:
// Not implemented per Slack documentation.
break
case .TeamMigrationStarted:
Client.sharedInstance.connect()
case .SubteamCreated, .SubteamUpdated:
EventHandler.subteam(event)
case .SubteamSelfAdded:
EventHandler.subteamAddedSelf(event)
case.SubteamSelfRemoved:
EventHandler.subteamRemovedSelf(event)
case .Error:
print("Error: \(event)")
break
}
}
}
static func messageDispatcher(event:Event) {
let subtype = MessageSubtype(rawValue: event.subtype!)!
switch subtype {
case .MessageChanged:
EventHandler.messageChanged(event)
case .MessageDeleted:
EventHandler.messageDeleted(event)
default:
EventHandler.messageReceived(event)
}
}
}
-600
View File
@@ -1,600 +0,0 @@
//
// EventHandler.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
internal struct EventHandler {
//MARK: - Initial connection
static func connected() {
Client.sharedInstance.connected = true
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.clientConnected()
}
}
//MARK: - Messages
static func messageSent(event: Event) {
if let reply = event.replyTo, message = Client.sharedInstance.sentMessages[NSNumber(double: reply).stringValue], channel = message.channel, ts = message.ts {
message.ts = event.ts
message.text = event.text
Client.sharedInstance.channels[channel]?.messages[ts] = message
if let delegate = Client.sharedInstance.messageEventsDelegate {
delegate.messageSent(message)
}
}
}
static func messageReceived(event: Event) {
if let channel = event.channel, message = event.message, id = channel.id, ts = message.ts {
Client.sharedInstance.channels[id]?.messages[ts] = message
if let delegate = Client.sharedInstance.messageEventsDelegate {
delegate.messageReceived(message)
}
}
}
static func messageChanged(event: Event) {
if let id = event.channel?.id, nested = event.nestedMessage, ts = nested.ts {
Client.sharedInstance.channels[id]?.messages[ts] = nested
if let delegate = Client.sharedInstance.messageEventsDelegate {
delegate.messageChanged(nested)
}
}
}
static func messageDeleted(event: Event) {
if let id = event.channel?.id, key = event.message?.deletedTs {
let message = Client.sharedInstance.channels[id]?.messages[key]
Client.sharedInstance.channels[id]?.messages.removeValueForKey(key)
if let delegate = Client.sharedInstance.messageEventsDelegate {
delegate.messageDeleted(message)
}
}
}
//MARK: - Channels
static func userTyping(event: Event) {
if let channelID = event.channel?.id, userID = event.user?.id {
if let _ = Client.sharedInstance.channels[channelID] {
if (!Client.sharedInstance.channels[channelID]!.usersTyping.contains(userID)) {
Client.sharedInstance.channels[channelID]?.usersTyping.append(userID)
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.userTyping(event.channel, user: event.user)
}
}
}
let timeout = dispatch_time(DISPATCH_TIME_NOW, Int64(5.0 * Double(NSEC_PER_SEC)))
dispatch_after(timeout, dispatch_get_main_queue()) {
if let index = Client.sharedInstance.channels[channelID]?.usersTyping.indexOf(userID) {
Client.sharedInstance.channels[channelID]?.usersTyping.removeAtIndex(index)
}
}
}
}
static func channelMarked(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id]?.lastRead = event.ts
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelMarked(channel, timestamp: event.ts)
}
}
//TODO: Recalculate unreads
}
static func channelCreated(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id] = channel
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelCreated(channel)
}
}
}
static func channelDeleted(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels.removeValueForKey(id)
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelDeleted(channel)
}
}
}
static func channelJoined(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id] = event.channel
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelJoined(channel)
}
}
}
static func channelLeft(event: Event) {
if let channel = event.channel, id = channel.id, userID = Client.sharedInstance.authenticatedUser?.id {
if let index = Client.sharedInstance.channels[id]?.members.indexOf(userID) {
Client.sharedInstance.channels[id]?.members.removeAtIndex(index)
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelLeft(channel)
}
}
}
}
static func channelRenamed(event: Event) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id]?.name = channel.name
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelRenamed(channel)
}
}
}
static func channelArchived(event: Event, archived: Bool) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id]?.isArchived = archived
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelArchived(channel)
}
}
}
static func channelHistoryChanged(event: Event) {
if let channel = event.channel {
//TODO: Reload chat history if there are any cached messages before latest
if let delegate = Client.sharedInstance.channelEventsDelegate {
delegate.channelHistoryChanged(channel)
}
}
}
//MARK: - Do Not Disturb
static func doNotDisturbUpdated(event: Event) {
if let dndStatus = event.dndStatus {
Client.sharedInstance.authenticatedUser?.doNotDisturbStatus = dndStatus
if let delegate = Client.sharedInstance.doNotDisturbEventsDelegate {
delegate.doNotDisturbUpdated(dndStatus)
}
}
}
static func doNotDisturbUserUpdated(event: Event) {
if let dndStatus = event.dndStatus, user = event.user, id = user.id {
Client.sharedInstance.users[id]?.doNotDisturbStatus = dndStatus
if let delegate = Client.sharedInstance.doNotDisturbEventsDelegate {
delegate.doNotDisturbUserUpdated(dndStatus, user: user)
}
}
}
//MARK: - IM & Group Open/Close
static func open(event: Event, open: Bool) {
if let channel = event.channel, id = channel.id {
Client.sharedInstance.channels[id]?.isOpen = open
if let delegate = Client.sharedInstance.groupEventsDelegate {
delegate.groupOpened(channel)
}
}
}
//MARK: - Files
static func processFile(event: Event) {
if let file = event.file, id = file.id {
if let comment = file.initialComment, commentID = comment.id {
if Client.sharedInstance.files[id]?.comments[commentID] == nil {
Client.sharedInstance.files[id]?.comments[commentID] = comment
}
}
Client.sharedInstance.files[id] = file
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileProcessed(file)
}
}
}
static func filePrivate(event: Event) {
if let file = event.file, id = file.id {
Client.sharedInstance.files[id]?.isPublic = false
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileMadePrivate(file)
}
}
}
static func deleteFile(event: Event) {
if let file = event.file, id = file.id {
if Client.sharedInstance.files[id] != nil {
Client.sharedInstance.files.removeValueForKey(id)
}
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileDeleted(file)
}
}
}
static func fileCommentAdded(event: Event) {
if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id {
Client.sharedInstance.files[id]?.comments[commentID] = comment
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileCommentAdded(file, comment: comment)
}
}
}
static func fileCommentEdited(event: Event) {
if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id {
Client.sharedInstance.files[id]?.comments[commentID]?.comment = comment.comment
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileCommentEdited(file, comment: comment)
}
}
}
static func fileCommentDeleted(event: Event) {
if let file = event.file, id = file.id, comment = event.comment, commentID = comment.id {
Client.sharedInstance.files[id]?.comments.removeValueForKey(commentID)
if let delegate = Client.sharedInstance.fileEventsDelegate {
delegate.fileCommentDeleted(file, comment: comment)
}
}
}
//MARK: - Pins
static func pinAdded(event: Event) {
if let id = event.channelID, item = event.item {
Client.sharedInstance.channels[id]?.pinnedItems.append(item)
if let delegate = Client.sharedInstance.pinEventsDelegate {
delegate.itemPinned(item, channel: Client.sharedInstance.channels[id])
}
}
}
static func pinRemoved(event: Event) {
if let id = event.channelID {
if let pins = Client.sharedInstance.channels[id]?.pinnedItems.filter({$0 != event.item}) {
Client.sharedInstance.channels[id]?.pinnedItems = pins
}
if let delegate = Client.sharedInstance.pinEventsDelegate {
delegate.itemUnpinned(event.item, channel: Client.sharedInstance.channels[id])
}
}
}
//MARK: - Stars
static func itemStarred(event: Event, star: Bool) {
if let item = event.item, type = item.type {
switch type {
case "message":
starMessage(item, star: star)
case "file":
starFile(item, star: star)
case "file_comment":
starComment(item)
default:
break
}
if let delegate = Client.sharedInstance.starEventsDelegate {
delegate.itemStarred(item, star: star)
}
}
}
static func starMessage(item: Item, star: Bool) {
if let message = item.message, ts = message.ts, channel = item.channel {
if let _ = Client.sharedInstance.channels[channel]?.messages[ts] {
Client.sharedInstance.channels[channel]?.messages[ts]?.isStarred = star
}
}
}
static func starFile(item: Item, star: Bool) {
if let file = item.file, id = file.id {
Client.sharedInstance.files[id]?.isStarred = star
if let stars = Client.sharedInstance.files[id]?.stars {
if star == true {
Client.sharedInstance.files[id]?.stars = stars + 1
} else {
if stars > 0 {
Client.sharedInstance.files[id]?.stars = stars - 1
}
}
}
}
}
static func starComment(item: Item) {
if let file = item.file, id = file.id, comment = item.comment, commentID = comment.id {
Client.sharedInstance.files[id]?.comments[commentID] = comment
}
}
//MARK: - Reactions
static func addedReaction(event: Event) {
if let item = event.item, type = item.type, key = event.reaction, userID = event.user?.id {
switch type {
case "message":
if let channel = item.channel, ts = item.ts {
if let message = Client.sharedInstance.channels[channel]?.messages[ts] {
if (message.reactions[key]) == nil {
message.reactions[key] = Reaction(name: event.reaction, user: userID)
} else {
message.reactions[key]?.users[userID] = userID
}
}
}
case "file":
if let id = item.file?.id, file = Client.sharedInstance.files[id] {
if file.reactions[key] == nil {
Client.sharedInstance.files[id]?.reactions[key] = Reaction(name: event.reaction, user: userID)
} else {
Client.sharedInstance.files[id]?.reactions[key]?.users[userID] = userID
}
}
case "file_comment":
if let id = item.file?.id, file = Client.sharedInstance.files[id], commentID = item.fileCommentID {
if file.comments[commentID]?.reactions[key] == nil {
Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key] = Reaction(name: event.reaction, user: userID)
} else {
Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users[userID] = userID
}
}
break
default:
break
}
if let delegate = Client.sharedInstance.reactionEventsDelegate {
delegate.reactionAdded(event.reaction, item: event.item)
}
}
}
static func removedReaction(event: Event) {
if let item = event.item, type = item.type, key = event.reaction, userID = event.user?.id {
switch type {
case "message":
if let channel = item.channel, ts = item.ts {
if let message = Client.sharedInstance.channels[channel]?.messages[ts] {
if (message.reactions[key]) != nil {
message.reactions[key]?.users.removeValueForKey(userID)
}
if (message.reactions[key]?.users.count == 0) {
message.reactions.removeValueForKey(key)
}
}
}
case "file":
if let itemFile = item.file, id = itemFile.id, file = Client.sharedInstance.files[id] {
if file.reactions[key] != nil {
Client.sharedInstance.files[id]?.reactions[key]?.users.removeValueForKey(userID)
}
if Client.sharedInstance.files[id]?.reactions[key]?.users.count == 0 {
Client.sharedInstance.files[id]?.reactions.removeValueForKey(key)
}
}
case "file_comment":
if let id = item.file?.id, file = Client.sharedInstance.files[id], commentID = item.fileCommentID {
if file.comments[commentID]?.reactions[key] != nil {
Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users.removeValueForKey(userID)
}
if Client.sharedInstance.files[id]?.comments[commentID]?.reactions[key]?.users.count == 0 {
Client.sharedInstance.files[id]?.comments[commentID]?.reactions.removeValueForKey(key)
}
}
break
default:
break
}
if let delegate = Client.sharedInstance.reactionEventsDelegate {
delegate.reactionAdded(event.reaction, item: event.item)
}
}
}
//MARK: - Preferences
static func changePreference(event: Event) {
if let name = event.name {
Client.sharedInstance.authenticatedUser?.preferences?[name] = event.value
if let delegate = Client.sharedInstance.slackEventsDelegate, value = event.value {
delegate.preferenceChanged(name, value: value)
}
}
}
//Mark: - User Change
static func userChange(event: Event) {
if let user = event.user, id = user.id {
let preferences = Client.sharedInstance.users[id]?.preferences
Client.sharedInstance.users[id] = user
Client.sharedInstance.users[id]?.preferences = preferences
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.userChanged(user)
}
}
}
//MARK: - User Presence
static func presenceChange(event: Event) {
if let user = event.user, id = user.id {
Client.sharedInstance.users[id]?.presence = event.presence
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.presenceChanged(user, presence: event.presence)
}
}
}
//MARK: - Team
static func teamJoin(event: Event) {
if let user = event.user, id = user.id {
Client.sharedInstance.users[id] = user
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamJoined(user)
}
}
}
static func teamPlanChange(event: Event) {
if let plan = event.plan {
Client.sharedInstance.team?.plan = plan
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamPlanChanged(plan)
}
}
}
static func teamPreferenceChange(event: Event) {
if let name = event.name {
Client.sharedInstance.team?.prefs?[name] = event.value
if let delegate = Client.sharedInstance.teamEventsDelegate, value = event.value {
delegate.teamPreferencesChanged(name, value: value)
}
}
}
static func teamNameChange(event: Event) {
if let name = event.name {
Client.sharedInstance.team?.name = name
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamNameChanged(name)
}
}
}
static func teamDomainChange(event: Event) {
if let domain = event.domain {
Client.sharedInstance.team?.domain = domain
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamDomainChanged(domain)
}
}
}
static func emailDomainChange(event: Event) {
if let domain = event.emailDomain {
Client.sharedInstance.team?.emailDomain = domain
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamEmailDomainChanged(domain)
}
}
}
static func emojiChanged(event: Event) {
//TODO: Call emoji.list here
if let delegate = Client.sharedInstance.teamEventsDelegate {
delegate.teamEmojiChanged()
}
}
//MARK: - Bots
static func bot(event: Event) {
if let bot = event.bot, id = bot.id {
Client.sharedInstance.bots[id] = bot
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.botEvent(bot)
}
}
}
//MARK: - Subteams
static func subteam(event: Event) {
if let subteam = event.subteam, id = subteam.id {
Client.sharedInstance.userGroups[id] = subteam
if let delegate = Client.sharedInstance.subteamEventsDelegate {
delegate.subteamEvent(subteam)
}
}
}
static func subteamAddedSelf(event: Event) {
if let subteamID = event.subteamID, _ = Client.sharedInstance.authenticatedUser?.userGroups {
Client.sharedInstance.authenticatedUser?.userGroups![subteamID] = subteamID
if let delegate = Client.sharedInstance.subteamEventsDelegate {
delegate.subteamSelfAdded(subteamID)
}
}
}
static func subteamRemovedSelf(event: Event) {
if let subteamID = event.subteamID {
Client.sharedInstance.authenticatedUser?.userGroups?.removeValueForKey(subteamID)
if let delegate = Client.sharedInstance.subteamEventsDelegate {
delegate.subteamSelfRemoved(subteamID)
}
}
}
//MARK: - Authenticated User
static func manualPresenceChange(event: Event) {
Client.sharedInstance.authenticatedUser?.presence = event.presence
if let delegate = Client.sharedInstance.slackEventsDelegate {
delegate.manualPresenceChanged(Client.sharedInstance.authenticatedUser, presence: event.presence)
}
}
}
-92
View File
@@ -1,92 +0,0 @@
//
// Message.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public enum ItemType: String {
case ChannelMessage = "C"
case PrivateGroupMessage = "G"
case File = "F"
case FileComments = "Fc"
}
public class Message {
public let type = "message"
public let subtype: String?
internal(set) public var ts: String?
public let user: String?
public let channel: String?
internal(set) public var hidden: Bool?
internal(set) public var text: String?
public let botID: String?
public let username: String?
public let icons: [String: AnyObject]?
public let deletedTs: String?
internal(set) var purpose: String?
internal(set) var topic: String?
internal(set) var name: String?
internal(set) var members: [String]?
internal(set) var oldName: String?
public let upload: Bool?
public let itemType: String?
internal(set) public var isStarred: Bool?
internal(set) var pinnedTo: [String]?
public let comment: Comment?
public let file: File?
internal(set) public var reactions = [String: Reaction]()
init?(message: [String: AnyObject]?) {
subtype = message?["subtype"] as? String
ts = message?["ts"] as? String
user = message?["user"] as? String
channel = message?["channel"] as? String
hidden = message?["hidden"] as? Bool
text = message?["text"] as? String
botID = message?["bot_id"] as? String
username = message?["username"] as? String
icons = message?["icons"] as? [String: AnyObject]
deletedTs = message?["deleted_ts"] as? String
purpose = message?["purpose"] as? String
topic = message?["topic"] as? String
name = message?["name"] as? String
members = message?["members"] as? [String]
oldName = message?["old_name"] as? String
upload = message?["upload"] as? Bool
itemType = message?["item_type"] as? String
isStarred = message?["is_starred"] as? Bool
pinnedTo = message?["pinned_to"] as? [String]
comment = Comment(comment: message?["comment"] as? [String: AnyObject])
file = File(file: message?["file"] as? [String: AnyObject])
if let messageReactions = message?["reactions"] as? [[String: AnyObject]] {
for react in messageReactions {
let reaction = Reaction(reaction: react)
self.reactions[reaction!.name!] = reaction
}
}
}
}
extension Message: Equatable {}
public func ==(lhs: Message, rhs: Message) -> Bool {
return lhs.ts == rhs.ts && lhs.user == rhs.user && lhs.text == rhs.text
}
-177
View File
@@ -1,177 +0,0 @@
//
// Types.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// MARK: - Edited
public struct Edited {
public let user: String?
public let ts: String?
internal init?(edited:[String: AnyObject]?) {
user = edited?["user"] as? String
ts = edited?["ts"] as? String
}
}
// MARK: - Reaction
public struct Reaction {
public let name: String?
internal(set) public var users = [String: String]()
internal init?(reaction:[String: AnyObject]?) {
name = reaction?["name"] as? String
}
internal init?(name: String?, user: String) {
self.name = name
users[user] = user
}
internal init?(name: String?, users: [String: String]) {
self.name = name
self.users = users
}
static func reactionsFromArray(array: [[String: AnyObject]]) -> [String: Reaction] {
var reactions = [String: Reaction]()
var userDictionary = [String: String]()
for reaction in array {
if let users = reaction["users"] as? [String] {
for user in users {
userDictionary[user] = user
}
}
if let name = reaction["name"] as? String {
reactions[name] = Reaction(name: name, users: userDictionary)
}
}
return reactions
}
}
extension Reaction: Equatable {}
public func ==(lhs: Reaction, rhs: Reaction) -> Bool {
return lhs.name == rhs.name
}
// MARK: - Comment
public struct Comment {
public let id: String?
public let user: String?
internal(set) public var created: Int?
internal(set) public var comment: String?
internal(set) public var starred: Bool?
internal(set) public var stars: Int?
internal(set) public var reactions = [String: Reaction]()
internal init?(comment:[String: AnyObject]?) {
id = comment?["id"] as? String
created = comment?["created"] as? Int
user = comment?["user"] as? String
starred = comment?["is_starred"] as? Bool
stars = comment?["num_stars"] as? Int
self.comment = comment?["comment"] as? String
}
internal init?(id: String?) {
self.id = id
self.user = nil
}
}
extension Comment: Equatable {}
public func ==(lhs: Comment, rhs: Comment) -> Bool {
return lhs.id == rhs.id
}
// MARK: - Item
public struct Item {
public let type: String?
public let ts: String?
public let channel: String?
public let message: Message?
public let file: File?
public let comment: Comment?
public let fileCommentID: String?
internal init?(item:[String: AnyObject]?) {
type = item?["type"] as? String
ts = item?["ts"] as? String
channel = item?["channel"] as? String
message = Message(message: item?["message"] as? [String: AnyObject])
// Comment and File can come across as Strings or Dictionaries
if (Comment(comment: item?["comment"] as? [String: AnyObject])?.id == nil) {
comment = Comment(id: item?["comment"] as? String)
} else {
comment = Comment(comment: item?["comment"] as? [String: AnyObject])
}
if (File(file: item?["file"] as? [String: AnyObject])?.id == nil) {
file = File(id: item?["file"] as? String)
} else {
file = File(file: item?["file"] as? [String: AnyObject])
}
fileCommentID = item?["file_comment"] as? String
}
}
extension Item: Equatable {}
public func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.type == rhs.type && lhs.channel == rhs.channel && lhs.file == rhs.file && lhs.comment == rhs.comment && lhs.message == rhs.message
}
// MARK: - Topic
public struct Topic {
public let value: String?
public let creator: String?
public let lastSet: Int?
internal init?(topic: [String: AnyObject]?) {
value = topic?["value"] as? String
creator = topic?["creator"] as? String
lastSet = topic?["last_set"] as? Int
}
}
// MARK: - Do Not Disturb Status
public struct DoNotDisturbStatus {
internal(set) public var enabled: Bool?
internal(set) public var nextDoNotDisturbStart: Int?
internal(set) public var nextDoNotDisturbEnd: Int?
internal(set) public var snoozeEnabled: Bool?
internal(set) public var snoozeEndtime: Int?
internal init?(status: [String: AnyObject]?) {
enabled = status?["dnd_enabled"] as? Bool
nextDoNotDisturbStart = status?["next_dnd_start_ts"] as? Int
nextDoNotDisturbEnd = status?["next_dnd_end_ts"] as? Int
snoozeEnabled = status?["snooze_enabled"] as? Bool
snoozeEndtime = status?["snooze_endtime"] as? Int
}
}
@@ -0,0 +1,175 @@
//
// Client+EventDispatching.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal extension Client {
func dispatch(_ anEvent: [String: Any]) {
let event = Event(anEvent)
let type = event.type ?? .unknown
switch type {
case .hello:
connected = true
connectionEventsDelegate?.connected(self)
case .ok:
messageSent(event)
case .message:
if (event.subtype != nil) {
messageDispatcher(event)
} else {
messageReceived(event)
}
case .userTyping:
userTyping(event)
case .channelMarked, .imMarked, .groupMarked:
channelMarked(event)
case .channelCreated, .imCreated:
channelCreated(event)
case .channelJoined, .groupJoined:
channelJoined(event)
case .channelLeft, .groupLeft:
channelLeft(event)
case .channelDeleted:
channelDeleted(event)
case .channelRenamed, .groupRename:
channelRenamed(event)
case .channelArchive, .groupArchive:
channelArchived(event, archived: true)
case .channelUnarchive, .groupUnarchive:
channelArchived(event, archived: false)
case .channelHistoryChanged, .imHistoryChanged, .groupHistoryChanged:
channelHistoryChanged(event)
case .dndUpdated:
doNotDisturbUpdated(event)
case .dndUpatedUser:
doNotDisturbUserUpdated(event)
case .imOpen, .groupOpen:
open(event, open: true)
case .imClose, .groupClose:
open(event, open: false)
case .fileCreated:
processFile(event)
case .fileShared:
processFile(event)
case .fileUnshared:
processFile(event)
case .filePublic:
processFile(event)
case .filePrivate:
filePrivate(event)
case .fileChanged:
processFile(event)
case .fileDeleted:
deleteFile(event)
case .fileCommentAdded:
fileCommentAdded(event)
case .fileCommentEdited:
fileCommentEdited(event)
case .fileCommentDeleted:
fileCommentDeleted(event)
case .pinAdded:
pinAdded(event)
case .pinRemoved:
pinRemoved(event)
case .pong:
pong(event)
case .presenceChange:
presenceChange(event)
case .manualPresenceChange:
manualPresenceChange(event)
case .prefChange:
changePreference(event)
case .userChange:
userChange(event)
case .teamJoin:
teamJoin(event)
case .starAdded:
itemStarred(event, star: true)
case .starRemoved:
itemStarred(event, star: false)
case .reactionAdded:
addedReaction(event)
case .reactionRemoved:
removedReaction(event)
case .emojiChanged:
emojiChanged(event)
case .commandsChanged:
// This functionality is only used by our web client.
// The other APIs required to support slash command metadata are currently unstable.
// Until they are released other clients should ignore this event.
break
case .teamPlanChange:
teamPlanChange(event)
case .teamPrefChange:
teamPreferenceChange(event)
case .teamRename:
teamNameChange(event)
case .teamDomainChange:
teamDomainChange(event)
case .emailDomainChange:
emailDomainChange(event)
case .teamProfileChange:
teamProfileChange(event)
case .teamProfileDelete:
teamProfileDeleted(event)
case .teamProfileReorder:
teamProfileReordered(event)
case .botAdded:
bot(event)
case .botChanged:
bot(event)
case .accountsChanged:
// The accounts_changed event is used by our web client to maintain a list of logged-in accounts.
// Other clients should ignore this event.
break
case .teamMigrationStarted:
connect(options: options ?? ClientOptions())
case .reconnectURL:
// The reconnect_url event is currently unsupported and experimental.
break
case .subteamCreated, .subteamUpdated:
subteam(event)
case .subteamSelfAdded:
subteamAddedSelf(event)
case .subteamSelfRemoved:
subteamRemovedSelf(event)
case .error:
print("Error: \(anEvent)")
case .unknown:
print("Unsupported event of type: \(anEvent["type"] ?? "No Type Information")")
}
}
func messageDispatcher(_ event:Event) {
guard let value = event.subtype, let subtype = MessageSubtype(rawValue:value) else {
return
}
switch subtype {
case .messageChanged:
messageChanged(event)
case .messageDeleted:
messageDeleted(event)
default:
messageReceived(event)
}
}
}
+562
View File
@@ -0,0 +1,562 @@
//
// Client+EventHandling.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
internal extension Client {
//MARK: - Pong
func pong(_ event: Event) {
pong = event.replyTo
}
//MARK: - Messages
func messageSent(_ event: Event) {
guard let reply = event.replyTo, let message = sentMessages[NSNumber(value: reply).stringValue], let channel = message.channel, let ts = message.ts else {
return
}
message.ts = event.ts
message.text = event.text
channels[channel]?.messages[ts] = message
messageEventsDelegate?.sent(message, client: self)
}
func messageReceived(_ event: Event) {
guard let channel = event.channel, let message = event.message, let id = channel.id, let ts = message.ts else {
return
}
channels[id]?.messages[ts] = message
messageEventsDelegate?.received(message, client:self)
}
func messageChanged(_ event: Event) {
guard let id = event.channel?.id, let nested = event.nestedMessage, let ts = nested.ts else {
return
}
channels[id]?.messages[ts] = nested
messageEventsDelegate?.changed(nested, client:self)
}
func messageDeleted(_ event: Event) {
guard let id = event.channel?.id, let key = event.message?.deletedTs, let message = channels[id]?.messages[key] else {
return
}
_ = channels[id]?.messages.removeValue(forKey: key)
messageEventsDelegate?.deleted(message, client:self)
}
//MARK: - Channels
func userTyping(_ event: Event) {
guard let channel = event.channel, let channelID = channel.id, let user = event.user, let userID = user.id ,
channels.index(forKey: channelID) != nil && !channels[channelID]!.usersTyping.contains(userID) else {
return
}
channels[channelID]?.usersTyping.append(userID)
channelEventsDelegate?.userTypingIn(channel, user: user, client: self)
let timeout = DispatchTime.now() + Double(Int64(5.0 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
DispatchQueue.main.asyncAfter(deadline: timeout, execute: {
if let index = self.channels[channelID]?.usersTyping.index(of: userID) {
self.channels[channelID]?.usersTyping.remove(at: index)
}
})
}
func channelMarked(_ event: Event) {
guard let channel = event.channel, let id = channel.id, let timestamp = event.ts else {
return
}
channels[id]?.lastRead = event.ts
channelEventsDelegate?.marked(channel, timestamp: timestamp, client: self)
}
func channelCreated(_ event: Event) {
guard let channel = event.channel, let id = channel.id else {
return
}
channels[id] = channel
channelEventsDelegate?.created(channel, client: self)
}
func channelDeleted(_ event: Event) {
guard let channel = event.channel, let id = channel.id else {
return
}
channels.removeValue(forKey: id)
channelEventsDelegate?.deleted(channel, client: self)
}
func channelJoined(_ event: Event) {
guard let channel = event.channel, let id = channel.id else {
return
}
channels[id] = event.channel
channelEventsDelegate?.joined(channel, client: self)
}
func channelLeft(_ event: Event) {
guard let channel = event.channel, let id = channel.id else {
return
}
if let userID = authenticatedUser?.id, let index = channels[id]?.members?.index(of: userID) {
channels[id]?.members?.remove(at: index)
}
channelEventsDelegate?.left(channel, client: self)
}
func channelRenamed(_ event: Event) {
guard let channel = event.channel, let id = channel.id else {
return
}
channels[id]?.name = channel.name
channelEventsDelegate?.renamed(channel, client: self)
}
func channelArchived(_ event: Event, archived: Bool) {
guard let channel = event.channel, let id = channel.id else {
return
}
channels[id]?.isArchived = archived
channelEventsDelegate?.archived(channel, client: self)
}
func channelHistoryChanged(_ event: Event) {
guard let channel = event.channel else {
return
}
channelEventsDelegate?.historyChanged(channel, client: self)
}
//MARK: - Do Not Disturb
func doNotDisturbUpdated(_ event: Event) {
guard let dndStatus = event.dndStatus else {
return
}
authenticatedUser?.doNotDisturbStatus = dndStatus
doNotDisturbEventsDelegate?.updated(dndStatus, client: self)
}
func doNotDisturbUserUpdated(_ event: Event) {
guard let dndStatus = event.dndStatus, let user = event.user, let id = user.id else {
return
}
users[id]?.doNotDisturbStatus = dndStatus
doNotDisturbEventsDelegate?.userUpdated(dndStatus, user: user, client: self)
}
//MARK: - IM & Group Open/Close
func open(_ event: Event, open: Bool) {
guard let channel = event.channel, let id = channel.id else {
return
}
channels[id]?.isOpen = open
groupEventsDelegate?.opened(channel, client: self)
}
//MARK: - Files
func processFile(_ event: Event) {
guard let file = event.file, let id = file.id else {
return
}
if let comment = file.initialComment, let commentID = comment.id {
if files[id]?.comments[commentID] == nil {
files[id]?.comments[commentID] = comment
}
}
files[id] = file
fileEventsDelegate?.processed(file, client: self)
}
func filePrivate(_ event: Event) {
guard let file = event.file, let id = file.id else {
return
}
files[id]?.isPublic = false
fileEventsDelegate?.madePrivate(file, client: self)
}
func deleteFile(_ event: Event) {
guard let file = event.file, let id = file.id else {
return
}
if files[id] != nil {
files.removeValue(forKey: id)
}
fileEventsDelegate?.deleted(file, client: self)
}
func fileCommentAdded(_ event: Event) {
guard let file = event.file, let id = file.id, let comment = event.comment, let commentID = comment.id else {
return
}
files[id]?.comments[commentID] = comment
fileEventsDelegate?.commentAdded(file, comment: comment, client: self)
}
func fileCommentEdited(_ event: Event) {
guard let file = event.file, let id = file.id, let comment = event.comment, let commentID = comment.id else {
return
}
files[id]?.comments[commentID]?.comment = comment.comment
fileEventsDelegate?.commentAdded(file, comment: comment, client: self)
}
func fileCommentDeleted(_ event: Event) {
guard let file = event.file, let id = file.id, let comment = event.comment, let commentID = comment.id else {
return
}
_ = files[id]?.comments.removeValue(forKey: commentID)
fileEventsDelegate?.commentDeleted(file, comment: comment, client: self)
}
//MARK: - Pins
func pinAdded(_ event: Event) {
guard let id = event.channelID, let item = event.item else {
return
}
channels[id]?.pinnedItems.append(item)
pinEventsDelegate?.pinned(item, channel: channels[id], client: self)
}
func pinRemoved(_ event: Event) {
guard let id = event.channelID, let item = event.item else {
return
}
if let pins = channels[id]?.pinnedItems.filter({$0 != item}) {
channels[id]?.pinnedItems = pins
}
pinEventsDelegate?.unpinned(item, channel: channels[id], client: self)
}
//MARK: - Stars
func itemStarred(_ event: Event, star: Bool) {
guard let item = event.item, let type = item.type else {
return
}
switch type {
case "message":
starMessage(item, star: star)
case "file":
starFile(item, star: star)
case "file_comment":
starComment(item)
default:
break
}
starEventsDelegate?.starred(item, starred: star, self)
}
func starMessage(_ item: Item, star: Bool) {
guard let message = item.message, let ts = message.ts, let channel = item.channel , channels[channel]?.messages[ts] != nil else {
return
}
channels[channel]?.messages[ts]?.isStarred = star
}
func starFile(_ item: Item, star: Bool) {
guard let file = item.file, let id = file.id else {
return
}
files[id]?.isStarred = star
if let stars = files[id]?.stars {
if star == true {
files[id]?.stars = stars + 1
} else {
if stars > 0 {
files[id]?.stars = stars - 1
}
}
}
}
func starComment(_ item: Item) {
guard let file = item.file, let id = file.id, let comment = item.comment, let commentID = comment.id else {
return
}
files[id]?.comments[commentID] = comment
}
//MARK: - Reactions
func addedReaction(_ event: Event) {
guard let item = event.item, let type = item.type, let reaction = event.reaction, let userID = event.user?.id, let itemUser = event.itemUser else {
return
}
switch type {
case "message":
guard let channel = item.channel, let ts = item.ts, let message = channels[channel]?.messages[ts] else {
return
}
message.reactions.append(Reaction(name: reaction, user: userID))
case "file":
guard let id = item.file?.id else {
return
}
files[id]?.reactions.append(Reaction(name: reaction, user: userID))
case "file_comment":
guard let id = item.file?.id, let commentID = item.fileCommentID else {
return
}
files[id]?.comments[commentID]?.reactions.append(Reaction(name: reaction, user: userID))
default:
break
}
reactionEventsDelegate?.added(reaction, item: item, itemUser: itemUser, client: self)
}
func removedReaction(_ event: Event) {
guard let item = event.item, let type = item.type, let key = event.reaction, let userID = event.user?.id, let itemUser = event.itemUser else {
return
}
switch type {
case "message":
guard let channel = item.channel, let ts = item.ts, let message = channels[channel]?.messages[ts] else {
return
}
message.reactions = message.reactions.filter({$0.name != key && $0.user != userID})
case "file":
guard let itemFile = item.file, let id = itemFile.id else {
return
}
files[id]?.reactions = files[id]!.reactions.filter({$0.name != key && $0.user != userID})
case "file_comment":
guard let id = item.file?.id, let commentID = item.fileCommentID else {
return
}
files[id]?.comments[commentID]?.reactions = files[id]!.comments[commentID]!.reactions.filter({$0.name != key && $0.user != userID})
default:
break
}
reactionEventsDelegate?.removed(key, item: item, itemUser: itemUser, client: self)
}
//MARK: - Preferences
func changePreference(_ event: Event) {
guard let name = event.name else {
return
}
authenticatedUser?.preferences?[name] = event.value
slackEventsDelegate?.preferenceChanged(name, value: event.value, client: self)
}
//Mark: - User Change
func userChange(_ event: Event) {
guard let user = event.user, let id = user.id else {
return
}
let preferences = users[id]?.preferences
users[id] = user
users[id]?.preferences = preferences
slackEventsDelegate?.userChanged(user, client: self)
}
//MARK: - User Presence
func presenceChange(_ event: Event) {
guard let user = event.user, let id = user.id, let presence = event.presence else {
return
}
users[id]?.presence = event.presence
slackEventsDelegate?.presenceChanged(user, presence: presence, client: self)
}
//MARK: - Team
func teamJoin(_ event: Event) {
guard let user = event.user, let id = user.id else {
return
}
users[id] = user
teamEventsDelegate?.userJoined(user, client: self)
}
func teamPlanChange(_ event: Event) {
guard let plan = event.plan else {
return
}
team?.plan = plan
teamEventsDelegate?.planChanged(plan, client: self)
}
func teamPreferenceChange(_ event: Event) {
guard let name = event.name else {
return
}
team?.prefs?[name] = event.value
teamEventsDelegate?.preferencesChanged(name, value: event.value, client: self)
}
func teamNameChange(_ event: Event) {
guard let name = event.name else {
return
}
team?.name = name
teamEventsDelegate?.nameChanged(name, client: self)
}
func teamDomainChange(_ event: Event) {
guard let domain = event.domain else {
return
}
team?.domain = domain
teamEventsDelegate?.domainChanged(domain, client: self)
}
func emailDomainChange(_ event: Event) {
guard let domain = event.emailDomain else {
return
}
team?.emailDomain = domain
teamEventsDelegate?.emailDomainChanged(domain, client: self)
}
func emojiChanged(_ event: Event) {
teamEventsDelegate?.emojiChanged(self)
}
//MARK: - Bots
func bot(_ event: Event) {
guard let bot = event.bot, let id = bot.id else {
return
}
bots[id] = bot
slackEventsDelegate?.botEvent(bot, client: self)
}
//MARK: - Subteams
func subteam(_ event: Event) {
guard let subteam = event.subteam, let id = subteam.id else {
return
}
userGroups[id] = subteam
subteamEventsDelegate?.event(subteam, client: self)
}
func subteamAddedSelf(_ event: Event) {
guard let subteamID = event.subteamID, let _ = authenticatedUser?.userGroups else {
return
}
authenticatedUser?.userGroups![subteamID] = subteamID
subteamEventsDelegate?.selfAdded(subteamID, client: self)
}
func subteamRemovedSelf(_ event: Event) {
guard let subteamID = event.subteamID else {
return
}
_ = authenticatedUser?.userGroups?.removeValue(forKey: subteamID)
subteamEventsDelegate?.selfRemoved(subteamID, client: self)
}
//MARK: - Team Profiles
func teamProfileChange(_ event: Event) {
guard let profile = event.profile else {
return
}
for user in users {
for key in profile.fields.keys {
users[user.0]?.profile?.customProfile?.fields[key]?.updateProfileField(profile.fields[key])
}
}
teamProfileEventsDelegate?.changed(profile, client: self)
}
func teamProfileDeleted(_ event: Event) {
guard let profile = event.profile else {
return
}
for user in users {
if let id = profile.fields.first?.0 {
users[user.0]?.profile?.customProfile?.fields[id] = nil
}
}
teamProfileEventsDelegate?.deleted(profile, client: self)
}
func teamProfileReordered(_ event: Event) {
guard let profile = event.profile else {
return
}
for user in users {
for key in profile.fields.keys {
users[user.0]?.profile?.customProfile?.fields[key]?.ordering = profile.fields[key]?.ordering
}
}
teamProfileEventsDelegate?.reordered(profile, client: self)
}
//MARK: - Authenticated User
func manualPresenceChange(_ event: Event) {
guard let presence = event.presence, let user = authenticatedUser else {
return
}
authenticatedUser?.presence = presence
slackEventsDelegate?.manualPresenceChanged(user, presence: presence, client: self)
}
}
+64
View File
@@ -0,0 +1,64 @@
//
// Client+Utilities.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public enum ClientError: Error {
case channelDoesNotExist
case userDoesNotExist
}
public extension Client {
//MARK: - User & Channel
public func getChannelIDWith(name: String) throws -> String {
guard let id = channels.filter({$0.1.name == strip(string:name)}).first?.0 else {
throw ClientError.channelDoesNotExist
}
return id
}
public func getUserIDWith(name: String) throws -> String {
guard let id = users.filter({$0.1.name == strip(string:name)}).first?.0 else {
throw ClientError.userDoesNotExist
}
return id
}
public func getImIDForUserWith(id: String, success: @escaping (_ imID: String?)->Void, failure: @escaping (SlackError)->Void) {
let ims = channels.filter{$0.1.isIM == true}
let channel = ims.filter{$0.1.user == id}.first
if let channel = channel {
success(channel.0)
} else {
webAPI.openIM(userID: id, success: success, failure: failure)
}
}
//MARK: - Utilities
internal func strip(string: String) -> String {
var strippedString = string
if string[string.startIndex] == "@" || string[string.startIndex] == "#" {
strippedString = string.substring(from: string.characters.index(string.startIndex, offsetBy: 1))
}
return strippedString
}
}
+263
View File
@@ -0,0 +1,263 @@
//
// Client.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import Starscream
public final class Client: WebSocketDelegate {
internal(set) public var connected = false
internal(set) public var authenticatedUser: User?
internal(set) public var team: Team?
internal(set) public var channels = [String: Channel]()
internal(set) public var users = [String: User]()
internal(set) public var userGroups = [String: UserGroup]()
internal(set) public var bots = [String: Bot]()
internal(set) public var files = [String: File]()
internal(set) public var sentMessages = [String: Message]()
public var token = "xoxp-SLACK_AUTH_TOKEN"
public var webAPI: WebAPI {
return WebAPI(token: token)
}
internal var webSocket: WebSocket?
private let pingPongQueue = DispatchQueue(label: "com.launchsoft.SlackKit")
internal var ping: Double?
internal var pong: Double?
internal var options: ClientOptions?
//MARK: - Delegates
public weak var connectionEventsDelegate: ConnectionEventsDelegate?
public weak var slackEventsDelegate: SlackEventsDelegate?
public weak var messageEventsDelegate: MessageEventsDelegate?
public weak var doNotDisturbEventsDelegate: DoNotDisturbEventsDelegate?
public weak var channelEventsDelegate: ChannelEventsDelegate?
public weak var groupEventsDelegate: GroupEventsDelegate?
public weak var fileEventsDelegate: FileEventsDelegate?
public weak var pinEventsDelegate: PinEventsDelegate?
public weak var starEventsDelegate: StarEventsDelegate?
public weak var reactionEventsDelegate: ReactionEventsDelegate?
public weak var teamEventsDelegate: TeamEventsDelegate?
public weak var subteamEventsDelegate: SubteamEventsDelegate?
public weak var teamProfileEventsDelegate: TeamProfileEventsDelegate?
// If you already have an API token
internal init(apiToken: String) {
self.token = apiToken
}
public func setAuthToken(_ token: String) {
self.token = token
}
public func connect(options: ClientOptions = ClientOptions()) {
self.options = options
webAPI.rtmStart(simpleLatest: options.simpleLatest, noUnreads: options.noUnreads, mpimAware: options.mpimAware, success: {(response) in
guard let socketURL = response["url"] as? String, let url = URL(string: socketURL) else {
return
}
self.initialSetup(JSON: response)
self.webSocket = WebSocket(url: url)
self.webSocket?.delegate = self
self.webSocket?.connect()
}, failure: {(error) in
self.connectionEventsDelegate?.connectionFailed(self, error: error)
})
}
public func disconnect() {
webSocket?.disconnect()
}
//MARK: - RTM Message send
public func send(message: String, channelID: String) {
guard connected else { return }
if let data = try? format(message: message, channel: channelID), let string = String(data: data, encoding: String.Encoding.utf8) {
webSocket?.write(string: string)
}
}
private func format(message: String, channel: String) throws -> Data {
let json: [String: Any] = [
"id": Date().slackTimestamp,
"type": "message",
"channel": channel,
"text": message.slackFormatEscaping
]
addSentMessage(json)
return try JSONSerialization.data(withJSONObject: json, options: [])
}
private func addSentMessage(_ dictionary: [String: Any]) {
var message = dictionary
guard let id = message["id"] as? NSNumber else {
return
}
let ts = String(describing: id)
message.removeValue(forKey: "id")
message["ts"] = ts
message["user"] = self.authenticatedUser?.id
sentMessages[ts] = Message(dictionary: message)
}
//MARK: - RTM Ping
private func pingRTMServerAt(interval: TimeInterval) {
let delay = DispatchTime.now() + Double(Int64(interval * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
pingPongQueue.asyncAfter(deadline: delay, execute: {
guard self.connected && self.timeoutCheck() else {
self.disconnect()
return
}
self.sendRTMPing()
self.pingRTMServerAt(interval: interval)
})
}
private func sendRTMPing() {
guard connected else {
return
}
let json: [String: Any] = [
"id": Date().slackTimestamp,
"type": "ping"
]
guard let data = try? JSONSerialization.data(withJSONObject: json, options: []) else {
return
}
if let string = String(data: data, encoding: String.Encoding.utf8) {
ping = json["id"] as? Double
webSocket?.write(string: string)
}
}
private func timeoutCheck() -> Bool {
if let pong = pong, let ping = ping, let timeout = options?.timeout {
if pong - ping < timeout {
return true
} else {
return false
}
// Ping-pong or timeout not configured
} else {
return true
}
}
//MARK: - Client setup
private func initialSetup(JSON: [String: Any]) {
team = Team(team: JSON["team"] as? [String: Any])
authenticatedUser = User(user: JSON["self"] as? [String: Any])
authenticatedUser?.doNotDisturbStatus = DoNotDisturbStatus(status: JSON["dnd"] as? [String: Any])
enumerateObjects(JSON["users"] as? Array) { (user) in self.addUser(user) }
enumerateObjects(JSON["channels"] as? Array) { (channel) in self.addChannel(channel) }
enumerateObjects(JSON["groups"] as? Array) { (group) in self.addChannel(group) }
enumerateObjects(JSON["mpims"] as? Array) { (mpim) in self.addChannel(mpim) }
enumerateObjects(JSON["ims"] as? Array) { (ims) in self.addChannel(ims) }
enumerateObjects(JSON["bots"] as? Array) { (bots) in self.addBot(bots) }
enumerateSubteams(JSON["subteams"] as? [String: Any])
}
private func addUser(_ aUser: [String: Any]) {
let user = User(user: aUser)
if let id = user.id {
users[id] = user
}
}
private func addChannel(_ aChannel: [String: Any]) {
let channel = Channel(channel: aChannel)
if let id = channel.id {
channels[id] = channel
}
}
private func addBot(_ aBot: [String: Any]) {
let bot = Bot(bot: aBot)
if let id = bot.id {
bots[id] = bot
}
}
private func enumerateSubteams(_ subteams: [String: Any]?) {
if let subteams = subteams {
if let all = subteams["all"] as? [[String: Any]] {
for item in all {
let u = UserGroup(userGroup: item)
if let id = u.id {
self.userGroups[id] = u
}
}
}
if let auth = subteams["self"] as? [String] {
for item in auth {
authenticatedUser?.userGroups = [String: String]()
authenticatedUser?.userGroups?[item] = item
}
}
}
}
// MARK: - Utilities
private func enumerateObjects(_ array: [Any]?, initalizer: ([String: Any])-> Void) {
if let array = array {
for object in array {
if let dictionary = object as? [String: Any] {
initalizer(dictionary)
}
}
}
}
// MARK: - WebSocketDelegate
public func websocketDidConnect(socket: WebSocket) {
if let pingInterval = options?.pingInterval {
pingRTMServerAt(interval: pingInterval)
}
}
public func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
connected = false
webSocket = nil
authenticatedUser = nil
connectionEventsDelegate?.disconnected(self)
if let options = options, options.reconnect == true {
connect(options: options)
}
}
public func websocketDidReceiveMessage(socket: WebSocket, text: String) {
guard let data = text.data(using: String.Encoding.utf8) else {
return
}
if let json = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)) as? [String: Any] {
dispatch(json)
}
}
public func websocketDidReceiveData(socket: WebSocket, data: Data) {}
}
+109
View File
@@ -0,0 +1,109 @@
//
// EventDelegate.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public protocol ConnectionEventsDelegate: class {
func connected(_ client: Client)
func disconnected(_ client: Client)
func connectionFailed(_ client: Client, error: SlackError)
}
public protocol MessageEventsDelegate: class {
func sent(_ message: Message, client: Client)
func received(_ message: Message, client: Client)
func changed(_ message: Message, client: Client)
func deleted(_ message: Message?, client: Client)
}
public protocol ChannelEventsDelegate: class {
func userTypingIn(_ channel: Channel, user: User, client: Client)
func marked(_ channel: Channel, timestamp: String, client: Client)
func created(_ channel: Channel, client: Client)
func deleted(_ channel: Channel, client: Client)
func renamed(_ channel: Channel, client: Client)
func archived(_ channel: Channel, client: Client)
func historyChanged(_ channel: Channel, client: Client)
func joined(_ channel: Channel, client: Client)
func left(_ channel: Channel, client: Client)
}
public protocol DoNotDisturbEventsDelegate: class {
func updated(_ status: DoNotDisturbStatus, client: Client)
func userUpdated(_ status: DoNotDisturbStatus, user: User, client: Client)
}
public protocol GroupEventsDelegate: class {
func opened(_ group: Channel, client: Client)
}
public protocol FileEventsDelegate: class {
func processed(_ file: File, client: Client)
func madePrivate(_ file: File, client: Client)
func deleted(_ file: File, client: Client)
func commentAdded(_ file: File, comment: Comment, client: Client)
func commentEdited(_ file: File, comment: Comment, client: Client)
func commentDeleted(_ file: File, comment: Comment, client: Client)
}
public protocol PinEventsDelegate: class {
func pinned(_ item: Item, channel: Channel?, client: Client)
func unpinned(_ item: Item, channel: Channel?, client: Client)
}
public protocol StarEventsDelegate: class {
func starred(_ item: Item, starred: Bool, _ client: Client)
}
public protocol ReactionEventsDelegate: class {
func added(_ reaction: String, item: Item, itemUser: String, client: Client)
func removed(_ reaction: String, item: Item, itemUser: String, client: Client)
}
public protocol SlackEventsDelegate: class {
func preferenceChanged(_ preference: String, value: Any?, client: Client)
func userChanged(_ user: User, client: Client)
func presenceChanged(_ user: User, presence: String, client: Client)
func manualPresenceChanged(_ user: User, presence: String, client: Client)
func botEvent(_ bot: Bot, client: Client)
}
public protocol TeamEventsDelegate: class {
func userJoined(_ user: User, client: Client)
func planChanged(_ plan: String, client: Client)
func preferencesChanged(_ preference: String, value: Any?, client: Client)
func nameChanged(_ name: String, client: Client)
func domainChanged(_ domain: String, client: Client)
func emailDomainChanged(_ domain: String, client: Client)
func emojiChanged(_ client: Client)
}
public protocol SubteamEventsDelegate: class {
func event(_ userGroup: UserGroup, client: Client)
func selfAdded(_ subteamID: String, client: Client)
func selfRemoved(_ subteamID: String, client: Client)
}
public protocol TeamProfileEventsDelegate: class {
func changed(_ profile: CustomProfile, client: Client)
func deleted(_ profile: CustomProfile, client: Client)
func reordered(_ profile: CustomProfile, client: Client)
}
+41
View File
@@ -0,0 +1,41 @@
//
// Extensions.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
public extension Date {
var slackTimestamp: Double {
return NSNumber(value: timeIntervalSince1970).doubleValue
}
}
internal extension String {
var slackFormatEscaping: String {
var escapedString = replacingOccurrences(of: "&", with: "&amp;")
escapedString = replacingOccurrences(of: "<", with: "&lt;")
escapedString = replacingOccurrences(of: ">", with: "&gt;")
return escapedString
}
}
+71
View File
@@ -0,0 +1,71 @@
//
// IncomingWebhook.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
public struct IncomingWebhook {
public let url: String?
public let channel: String?
public let configurationURL: String?
public let username: String?
public let iconEmoji: String?
public let iconURL: String?
internal init(webhook: [String: Any]?) {
url = webhook?["url"] as? String
channel = webhook?["channel"] as? String
configurationURL = webhook?["configuration_url"] as? String
username = webhook?["username"] as? String
iconEmoji = webhook?["icon_emoji"] as? String
iconURL = webhook?["icon_url"] as? String
}
public init(url: String, channel: String? = nil, username: String? = nil, iconEmoji: String? = nil, iconURL: String? = nil) {
self.url = url
self.channel = channel
self.username = username
self.iconEmoji = iconEmoji
self.iconURL = iconURL
self.configurationURL = nil
}
public func postMessage(_ response: Response, success: ((Bool)->Void)? = nil, failure: ((SlackError)->Void)? = nil) {
if let url = self.url, let data = try? JSONSerialization.data(withJSONObject: jsonBody(response.json), options: []) {
NetworkInterface().customRequest(url, data: data, success: { _ in
success?(true)
}, errorClosure: {(error) in
failure?(error)
})
}
}
private func jsonBody(_ response: [String: Any]) -> [String: Any] {
var json = response
json["channel"] = channel
json["username"] = username
json["icon_emoji"] = iconEmoji
json["icon_url"] = iconURL
return json
}
}
@@ -0,0 +1,39 @@
//
// MessageActionResponder.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct MessageActionResponder {
public var responses:[(Action, Response)]
public init(responses:[(Action, Response)]) {
self.responses = responses
}
internal func responseForRequest(_ request:MessageActionRequest) -> Reply? {
if let response = responses.filter({$0.0.name == request.action?.name}).first?.1 {
return Reply.json(response: response)
} else {
return nil
}
}
}
@@ -0,0 +1,45 @@
//
// MessageActionServer.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
open class MessageActionServer: Server {
internal let responder: MessageActionResponder
required public init(token: String, route: String, responder: MessageActionResponder) {
self.responder = responder
super.init(token: token)
addRoute(route)
}
internal func addRoute(_ route: String) {
http.POST["/\(route)"] = { request in
let payload = request.parseUrlencodedForm()
let actionRequest = MessageActionRequest(response: self.jsonFromRequest(payload[0].1))
if let reply = self.responder.responseForRequest(actionRequest), actionRequest.token == self.token {
return self.request(actionRequest, reply: reply)
} else {
return .badRequest(.text("Bad request."))
}
}
}
}
+103
View File
@@ -0,0 +1,103 @@
//
// Action.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct Action {
public let name: String?
public let text: String?
public let type: String?
public let value: String?
public let style: ActionStyle?
public let confirm: Confirm?
internal init(action:[String: Any]?) {
name = action?["name"] as? String
text = action?["text"] as? String
type = action?["type"] as? String
value = action?["value"] as? String
style = ActionStyle(rawValue: action?["style"] as? String ?? "")
confirm = Confirm(confirm:action?["confirm"] as? [String: Any])
}
public init(name: String, text: String, style: ActionStyle = .defaultStyle, value: String? = nil, confirm: Confirm? = nil) {
self.type = "button"
self.name = name
self.text = text
self.value = value
self.style = style
self.confirm = confirm
}
internal var dictionary: [String: Any] {
var dict = [String: Any]()
dict["name"] = name
dict["text"] = text
dict["type"] = type
dict["value"] = value
dict["style"] = style?.rawValue
dict["confirm"] = confirm?.dictionary
return dict
}
public struct Confirm {
public let title: String?
public let text: String?
public let okText: String?
public let dismissText: String?
internal init(confirm:[String: Any]?) {
title = confirm?["title"] as? String
text = confirm?["text"] as? String
okText = confirm?["ok_text"] as? String
dismissText = confirm?["dismiss_text"] as? String
}
public init(text: String, title: String? = nil, okText: String? = nil, dismissText: String? = nil) {
self.text = text
self.title = title
self.okText = okText
self.dismissText = dismissText
}
internal var dictionary: [String: Any] {
var dict = [String: Any]()
dict["title"] = title
dict["text"] = text
dict["ok_text"] = okText
dict["dismiss_text"] = dismissText
return dict
}
}
}
public enum ActionStyle: String {
case defaultStyle = "default"
case primary = "primary"
case danger = "danger"
}
public enum ResponseType: String {
case inChannel = "in_channel"
case ephemeral = "ephemeral"
}
+131
View File
@@ -0,0 +1,131 @@
//
// Attachment.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct Attachment {
public let fallback: String?
public let callbackID: String?
public let type: String?
public let color: String?
public let pretext: String?
public let authorName: String?
public let authorLink: String?
public let authorIcon: String?
public let title: String?
public let titleLink: String?
public let text: String?
public let fields: [AttachmentField]?
public let actions: [Action]?
public let imageURL: String?
public let thumbURL: String?
public let footer: String?
public let footerIcon: String?
public let ts: Int?
public let markdownEnabledFields: Set<AttachmentTextField>?
internal init(attachment: [String: Any]?) {
fallback = attachment?["fallback"] as? String
callbackID = attachment?["callback_id"] as? String
type = attachment?["attachment_type"] as? String
color = attachment?["color"] as? String
pretext = attachment?["pretext"] as? String
authorName = attachment?["author_name"] as? String
authorLink = attachment?["author_link"] as? String
authorIcon = attachment?["author_icon"] as? String
title = attachment?["title"] as? String
titleLink = attachment?["title_link"] as? String
text = attachment?["text"] as? String
imageURL = attachment?["image_url"] as? String
thumbURL = attachment?["thumb_url"] as? String
footer = attachment?["footer"] as? String
footerIcon = attachment?["footer_icon"] as? String
ts = attachment?["ts"] as? Int
fields = (attachment?["fields"] as? [[String: Any]])?.map { AttachmentField(field: $0) }
actions = (attachment?["actions"] as? [[String: Any]])?.map { Action(action: $0) }
markdownEnabledFields = (attachment?["mrkdwn_in"] as? [String]).map { Set($0.flatMap(AttachmentTextField.init)) }
}
public init(fallback: String, title: String?, callbackID: String? = nil, type: String? = nil, colorHex: String? = nil, pretext: String? = nil, authorName: String? = nil, authorLink: String? = nil, authorIcon: String? = nil, titleLink: String? = nil, text: String? = nil, fields: [AttachmentField]? = nil, actions: [Action]? = nil, imageURL: String? = nil, thumbURL: String? = nil, footer: String? = nil, footerIcon:String? = nil, ts:Int? = nil, markdownFields: Set<AttachmentTextField>? = nil) {
self.fallback = fallback
self.callbackID = callbackID
self.type = type
self.color = colorHex
self.pretext = pretext
self.authorName = authorName
self.authorLink = authorLink
self.authorIcon = authorIcon
self.title = title
self.titleLink = titleLink
self.text = text
self.fields = fields
self.actions = actions
self.imageURL = imageURL
self.thumbURL = thumbURL
self.footer = footer
self.footerIcon = footerIcon
self.ts = ts
self.markdownEnabledFields = markdownFields
}
internal var dictionary: [String: Any] {
var attachment = [String: Any]()
attachment["fallback"] = fallback
attachment["callback_id"] = callbackID
attachment["attachment_type"] = type
attachment["color"] = color
attachment["pretext"] = pretext
attachment["author_name"] = authorName
attachment["author_link"] = authorLink
attachment["author_icon"] = authorIcon
attachment["title"] = title
attachment["title_link"] = titleLink
attachment["text"] = text
attachment["fields"] = fields?.map{$0.dictionary}
attachment["actions"] = actions?.map{$0.dictionary}
attachment["image_url"] = imageURL
attachment["thumb_url"] = thumbURL
attachment["footer"] = footer
attachment["footer_icon"] = footerIcon
attachment["ts"] = ts
attachment["mrkdwn_in"] = markdownEnabledFields?.map { $0.rawValue }
return attachment
}
}
public enum AttachmentColor: String {
case good = "good"
case warning = "warning"
case danger = "danger"
}
public enum AttachmentTextField: String {
case fallback = "fallback"
case pretext = "pretext"
case authorName = "author_name"
case title = "title"
case text = "text"
case fields = "fields"
case footer = "footer"
}
@@ -0,0 +1,49 @@
//
// AttachmentField.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct AttachmentField {
public let title: String?
public let value: String?
public let short: Bool?
internal init(field: [String: Any]?) {
title = field?["title"] as? String
value = field?["value"] as? String
short = field?["short"] as? Bool
}
public init(title: String?, value: String?, short: Bool? = nil) {
self.title = title
self.value = value?.slackFormatEscaping
self.short = short
}
internal var dictionary: [String: Any] {
var field = [String: Any]()
field["title"] = title
field["value"] = value
field["short"] = short
return field
}
}
@@ -0,0 +1,47 @@
//
// AuthorizeRequest.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal struct AuthorizeRequest {
let clientID: String
let scope: [Scope]
let redirectURI: String
let state: String
let team: String?
var parameters: [String: Any] {
var json = [String : Any]()
json["scope"] = scope.map({$0.rawValue}).joined(separator: ",")
json["state"] = state
json["team"] = team
return json
}
init(clientID: String, scope:[Scope], redirectURI: String, state: String = "slackkit", team: String? = nil) {
self.clientID = clientID
self.scope = scope
self.redirectURI = redirectURI
self.state = state
self.team = team
}
}
@@ -0,0 +1,36 @@
//
// AuthorizeResponse.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal struct AuthorizeResponse {
let code: String
let state: String
init?(queryParameters: [(String, String)]) {
guard let code = queryParameters.first?.1, let state = queryParameters.last?.1 else {
return nil
}
self.code = code
self.state = state
}
}
@@ -0,0 +1,44 @@
//
// OAuthResponse.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal struct OAuthResponse {
let accessToken: String?
let scope: [Scope]?
let userID: String?
let teamName: String?
let teamID: String?
let incomingWebhook: IncomingWebhook?
let bot: Bot?
init(response: [String: Any]?) {
accessToken = response?["access_token"] as? String
scope = (response?["scope"] as? String)?.components(separatedBy: ",").flatMap{Scope(rawValue:$0)}
userID = response?["user_id"] as? String
teamName = response?["team_name"] as? String
teamID = response?["team_id"] as? String
incomingWebhook = IncomingWebhook(webhook: response?["incoming_webhook"] as? [String: Any])
bot = Bot(botUser: response?["bot"] as? [String: Any])
}
}
+71
View File
@@ -0,0 +1,71 @@
//
// Scope.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public enum Scope: String {
case ChannelsHistory = "channels:history"
case ChannelsRead = "channels:read"
case ChannelsWrite = "channels:write"
case ChatWriteBot = "chat:write:bot"
case ChatWriteUser = "chat:write:user"
case DNDRead = "dnd:read"
case DNDWrite = "dnd:write"
case EmojiRead = "emoji:read"
case FilesRead = "files:read"
case FilesWriteUser = "files:write:user"
case GroupsHistory = "groups:history"
case GroupsRead = "groups:read"
case GroupsWrite = "groups:write"
case IdentityBasic = "identity.basic"
case IMHistory = "im:history"
case IMRead = "im:read"
case IMWrite = "im:write"
case MPIMHistory = "mpim:history"
case MPIMRead = "mpim:read"
case MPIMWrite = "mpim:write"
case PinsRead = "pins:read"
case PinsWrite = "pins:write"
case ReactionsRead = "reactions:read"
case ReactionsWrite = "reactions:write"
case RemindersRead = "reminders:read"
case RemindersWrite = "reminders:write"
case SearchRead = "search:read"
case StarsRead = "stars:read"
case StarsWrite = "stars:write"
case TeamRead = "team:read"
case UserGroupsRead = "usergroups:read"
case UserGroupsWrite = "usergroups:write"
case UserProfilesRead = "user.profiles:read"
case UserProfilesWrite = "user.profiles:write"
case UsersRead = "users:read"
case UsersWrite = "users:write"
case IncomingWebhook = "incoming-webhook"
case Commands = "commands"
case Bot = "bot"
case Identify = "identify"
case Client = "client"
case Admin = "admin"
//Deprecated
case Read = "read"
case Post = "post"
}
@@ -24,13 +24,18 @@
public struct Bot {
public let id: String?
internal(set) public var botToken: String?
internal(set) public var name: String?
internal(set) public var icons: [String: AnyObject]?
internal(set) public var icons: [String: Any]?
internal init?(bot: [String: AnyObject]?) {
internal init(bot: [String: Any]?) {
id = bot?["id"] as? String
name = bot?["name"] as? String
icons = bot?["icons"] as? [String: AnyObject]
icons = bot?["icons"] as? [String: Any]
}
internal init(botUser: [String: Any]?) {
id = botUser?["bot_user_id"] as? String
botToken = botUser?["bot_access_token"] as? String
}
}
@@ -38,18 +38,18 @@ public struct Channel {
internal(set) public var topic: Topic?
internal(set) public var purpose: Topic?
internal(set) public var isMember: Bool?
internal(set) public var lastRead: String?
public var lastRead: String?
internal(set) public var latest: Message?
internal(set) public var unread: Int?
internal(set) public var unreadCountDisplay: Int?
public var unread: Int?
public var unreadCountDisplay: Int?
internal(set) public var hasPins: Bool?
internal(set) public var members = [String]()
internal(set) public var members: [String]?
// Client use
internal(set) public var pinnedItems = [Item]()
internal(set) public var usersTyping = [String]()
internal(set) public var messages = [String: Message]()
internal init?(channel: [String: AnyObject]?) {
internal init(channel: [String: Any]?) {
id = channel?["id"] as? String
name = channel?["name"] as? String
created = channel?["created"] as? Int
@@ -62,22 +62,23 @@ public struct Channel {
isUserDeleted = channel?["is_user_deleted"] as? Bool
user = channel?["user"] as? String
isOpen = channel?["is_open"] as? Bool
topic = Topic(topic: channel?["topic"] as? [String: AnyObject])
purpose = Topic(topic: channel?["purpose"] as? [String: AnyObject])
topic = Topic(topic: channel?["topic"] as? [String: Any])
purpose = Topic(topic: channel?["purpose"] as? [String: Any])
isMember = channel?["is_member"] as? Bool
lastRead = channel?["last_read"] as? String
latest = Message(message: channel?["latest"] as? [String: AnyObject])
unread = channel?["unread_count"] as? Int
unreadCountDisplay = channel?["unread_count_display"] as? Int
hasPins = channel?["has_pins"] as? Bool
if let members = channel?["members"] as? [String] {
self.members = members
members = channel?["members"] as? [String]
if let latestMesssageDictionary = channel?["latest"] as? [String: Any] {
latest = Message(dictionary: latestMesssageDictionary)
} else {
latest = Message(ts: channel?["latest"] as? String)
}
}
internal init?(id:String?) {
internal init(id:String?) {
self.id = id
created = nil
creator = nil
@@ -0,0 +1,43 @@
//
// ClientOptions.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
public struct ClientOptions {
let simpleLatest: Bool?
let noUnreads: Bool?
let mpimAware: Bool?
let pingInterval: TimeInterval?
let timeout: TimeInterval?
let reconnect: Bool?
public init(simpleLatest: Bool? = nil, noUnreads: Bool? = nil, mpimAware: Bool? = nil, pingInterval: TimeInterval? = nil, timeout: TimeInterval? = nil, reconnect: Bool? = nil) {
self.simpleLatest = simpleLatest
self.noUnreads = noUnreads
self.mpimAware = mpimAware
self.pingInterval = pingInterval
self.timeout = timeout
self.reconnect = reconnect
}
}
+51
View File
@@ -0,0 +1,51 @@
//
// Comment.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct Comment: Equatable {
public let id: String?
public let user: String?
internal(set) public var created: Int?
internal(set) public var comment: String?
internal(set) public var starred: Bool?
internal(set) public var stars: Int?
internal(set) public var reactions = [Reaction]()
internal init(comment:[String: Any]?) {
id = comment?["id"] as? String
created = comment?["created"] as? Int
user = comment?["user"] as? String
starred = comment?["is_starred"] as? Bool
stars = comment?["num_stars"] as? Int
self.comment = comment?["comment"] as? String
}
internal init(id: String?) {
self.id = id
self.user = nil
}
public static func ==(lhs: Comment, rhs: Comment) -> Bool {
return lhs.id == rhs.id
}
}
@@ -0,0 +1,50 @@
//
// CustomProfile.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct CustomProfile {
internal(set) public var fields = [String: CustomProfileField]()
internal init(profile: [String: Any]?) {
if let eventFields = profile?["fields"] as? [Any] {
for field in eventFields {
var cpf: CustomProfileField?
if let fieldDictionary = field as? [String: Any] {
cpf = CustomProfileField(field: fieldDictionary)
} else {
cpf = CustomProfileField(id: field as? String)
}
if let id = cpf?.id { fields[id] = cpf }
}
}
}
internal init(customFields: [String: Any]?) {
if let customFields = customFields {
for key in customFields.keys {
let cpf = CustomProfileField(field: customFields[key] as? [String: Any])
self.fields[key] = cpf
}
}
}
}
@@ -0,0 +1,66 @@
//
// CustomProfileField.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct CustomProfileField {
internal(set) public var id: String?
internal(set) public var alt: String?
internal(set) public var value: String?
internal(set) public var hidden: Bool?
internal(set) public var hint: String?
internal(set) public var label: String?
internal(set) public var options: String?
internal(set) public var ordering: Int?
internal(set) public var possibleValues: [String]?
internal(set) public var type: String?
internal init(field: [String: Any]?) {
id = field?["id"] as? String
alt = field?["alt"] as? String
value = field?["value"] as? String
hidden = field?["is_hidden"] as? Bool
hint = field?["hint"] as? String
label = field?["label"] as? String
options = field?["options"] as? String
ordering = field?["ordering"] as? Int
possibleValues = field?["possible_values"] as? [String]
type = field?["type"] as? String
}
internal init(id: String?) {
self.id = id
}
internal mutating func updateProfileField(_ profile: CustomProfileField?) {
id = profile?.id != nil ? profile?.id : id
alt = profile?.alt != nil ? profile?.alt : alt
value = profile?.value != nil ? profile?.value : value
hidden = profile?.hidden != nil ? profile?.hidden : hidden
hint = profile?.hint != nil ? profile?.hint : hint
label = profile?.label != nil ? profile?.label : label
options = profile?.options != nil ? profile?.options : options
ordering = profile?.ordering != nil ? profile?.ordering : ordering
possibleValues = profile?.possibleValues != nil ? profile?.possibleValues : possibleValues
type = profile?.type != nil ? profile?.type : type
}
}
@@ -0,0 +1,39 @@
//
// DoNotDisturbStatus.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct DoNotDisturbStatus {
internal(set) public var enabled: Bool?
internal(set) public var nextDoNotDisturbStart: Int?
internal(set) public var nextDoNotDisturbEnd: Int?
internal(set) public var snoozeEnabled: Bool?
internal(set) public var snoozeEndtime: Int?
internal init(status: [String: Any]?) {
enabled = status?["dnd_enabled"] as? Bool
nextDoNotDisturbStart = status?["next_dnd_start_ts"] as? Int
nextDoNotDisturbEnd = status?["next_dnd_end_ts"] as? Int
snoozeEnabled = status?["snooze_enabled"] as? Bool
snoozeEndtime = status?["snooze_endtime"] as? Int
}
}
@@ -1,5 +1,5 @@
//
// SlackKit.h
// Edited.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
@@ -21,10 +21,13 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
//! Project version number for SlackKit.
FOUNDATION_EXPORT double SlackKitVersionNumber;
//! Project version string for SlackKit.
FOUNDATION_EXPORT const unsigned char SlackKitVersionString[];
public struct Edited {
public let user: String?
public let ts: String?
internal init(edited:[String: Any]?) {
user = edited?["user"] as? String
ts = edited?["ts"] as? String
}
}
+217
View File
@@ -0,0 +1,217 @@
//
// Event.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal enum EventType: String {
case hello = "hello"
case message = "message"
case userTyping = "user_typing"
case channelMarked = "channel_marked"
case channelCreated = "channel_created"
case channelJoined = "channel_joined"
case channelLeft = "channel_left"
case channelDeleted = "channel_deleted"
case channelRenamed = "channel_rename"
case channelArchive = "channel_archive"
case channelUnarchive = "channel_unarchive"
case channelHistoryChanged = "channel_history_changed"
case dndUpdated = "dnd_updated"
case dndUpatedUser = "dnd_updated_user"
case imCreated = "im_created"
case imOpen = "im_open"
case imClose = "im_close"
case imMarked = "im_marked"
case imHistoryChanged = "im_history_changed"
case groupJoined = "group_joined"
case groupLeft = "group_left"
case groupOpen = "group_open"
case groupClose = "group_close"
case groupArchive = "group_archive"
case groupUnarchive = "group_unarchive"
case groupRename = "group_rename"
case groupMarked = "group_marked"
case groupHistoryChanged = "group_history_changed"
case fileCreated = "file_created"
case fileShared = "file_shared"
case fileUnshared = "file_unshared"
case filePublic = "file_public"
case filePrivate = "file_private"
case fileChanged = "file_change"
case fileDeleted = "file_deleted"
case fileCommentAdded = "file_comment_added"
case fileCommentEdited = "file_comment_edited"
case fileCommentDeleted = "file_comment_deleted"
case pinAdded = "pin_added"
case pinRemoved = "pin_removed"
case pong = "pong"
case presenceChange = "presence_change"
case manualPresenceChange = "manual_presence_change"
case prefChange = "pref_change"
case userChange = "user_change"
case teamJoin = "team_join"
case starAdded = "star_added"
case starRemoved = "star_removed"
case reactionAdded = "reaction_added"
case reactionRemoved = "reaction_removed"
case emojiChanged = "emoji_changed"
case commandsChanged = "commands_changed"
case teamPlanChange = "team_plan_change"
case teamPrefChange = "team_pref_change"
case teamRename = "team_rename"
case teamDomainChange = "team_domain_change"
case emailDomainChange = "email_domain_change"
case teamProfileChange = "team_profile_change"
case teamProfileDelete = "team_profile_delete"
case teamProfileReorder = "team_profile_reorder"
case botAdded = "bot_added"
case botChanged = "bot_changed"
case accountsChanged = "accounts_changed"
case teamMigrationStarted = "team_migration_started"
case reconnectURL = "reconnect_url"
case subteamCreated = "subteam_created"
case subteamUpdated = "subteam_updated"
case subteamSelfAdded = "subteam_self_added"
case subteamSelfRemoved = "subteam_self_removed"
case ok = "ok"
case error = "error"
case unknown = "unknown"
}
internal enum MessageSubtype: String {
case botMessage = "bot_message"
case meMessage = "me_message"
case messageChanged = "message_changed"
case messageDeleted = "message_deleted"
case channelJoin = "channel_join"
case channelLeave = "channel_leave"
case channelTopic = "channel_topic"
case channelPurpose = "channel_purpose"
case channelName = "channel_name"
case channelArchive = "channel_archive"
case channelUnarchive = "channel_unarchive"
case groupJoin = "group_join"
case groupLeave = "group_leave"
case groupTopic = "group_topic"
case groupPurpose = "group_purpose"
case groupName = "group_name"
case groupArchive = "group_archive"
case groupUnarchive = "group_unarchive"
case fileShare = "file_share"
case fileComment = "file_comment"
case fileMention = "file_mention"
case pinnedItem = "pinned_item"
case unpinnedItem = "unpinned_item"
}
internal class Event {
let type: EventType?
let ts: String?
let subtype: String?
let channelID: String?
let text: String?
let eventTs: String?
let latest: String?
let hidden: Bool?
let isStarred: Bool?
let hasPins: Bool?
let pinnedTo: [String]?
let fileID: String?
let presence: String?
let name: String?
let value: Any?
let plan: String?
let url: String?
let domain: String?
let emailDomain: String?
let reaction: String?
let replyTo: Double?
let reactions: [[String: Any]]?
let edited: Edited?
let bot: Bot?
let channel: Channel?
let comment: Comment?
let user: User?
let file: File?
let message: Message?
let nestedMessage: Message?
let itemUser: String?
let item: Item?
let dndStatus: DoNotDisturbStatus?
let subteam: UserGroup?
let subteamID: String?
var profile: CustomProfile?
init(_ event:[String: Any]) {
type = EventType(rawValue: event["type"] as? String ?? "ok")
ts = event["ts"] as? String
subtype = event["subtype"] as? String
channelID = event["channel_id"] as? String
text = event["text"] as? String
eventTs = event["event_ts"] as? String
latest = event["latest"] as? String
hidden = event["hidden"] as? Bool
isStarred = event["is_starred"] as? Bool
hasPins = event["has_pins"] as? Bool
pinnedTo = event["pinned_top"] as? [String]
fileID = event["file_id"] as? String
presence = event["presence"] as? String
name = event["name"] as? String
value = event["value"]
plan = event["plan"] as? String
url = event["url"] as? String
domain = event["domain"] as? String
emailDomain = event["email_domain"] as? String
reaction = event["reaction"] as? String
replyTo = event["reply_to"] as? Double
reactions = event["reactions"] as? [[String: Any]]
bot = Bot(bot: event["bot"] as? [String: Any])
edited = Edited(edited:event["edited"] as? [String: Any])
dndStatus = DoNotDisturbStatus(status: event["dnd_status"] as? [String: Any])
itemUser = event["item_user"] as? String
item = Item(item: event["item"] as? [String: Any])
subteam = UserGroup(userGroup: event["subteam"] as? [String: Any])
subteamID = event["subteam_id"] as? String
message = Message(dictionary: event)
nestedMessage = Message(dictionary: event["message"] as? [String: Any])
profile = CustomProfile(profile: event["profile"] as? [String: Any])
file = File(id: event["file"] as? String)
// Comment, Channel, and User can come across as Strings or Dictionaries
if let commentDictionary = event["comment"] as? [String: Any] {
comment = Comment(comment: commentDictionary)
} else {
comment = Comment(id: event["comment"] as? String)
}
if let userDictionary = event["user"] as? [String: Any] {
user = User(user: userDictionary)
} else {
user = User(id: event["user"] as? String)
}
if let channelDictionary = event["channel"] as? [String: Any] {
channel = Channel(channel: channelDictionary)
} else {
channel = Channel(id: event["channel"] as? String)
}
}
}
@@ -21,8 +21,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct File {
public struct File: Equatable {
public let id: String?
public let created: Int?
public let name: String?
@@ -36,8 +36,6 @@ public struct File {
public let isExternal: Bool?
public let externalType: String?
public let size: Int?
public let url: String?
public let urlDownload: String?
public let urlPrivate: String?
public let urlPrivateDownload: String?
public let thumb64: String?
@@ -46,6 +44,22 @@ public struct File {
public let thumb360gif: String?
public let thumb360w: String?
public let thumb360h: String?
public let thumb480: String?
public let thumb480gif: String?
public let thumb480w: String?
public let thumb480h: String?
public let thumb720: String?
public let thumb720gif: String?
public let thumb720w: String?
public let thumb720h: String?
public let thumb960: String?
public let thumb960gif: String?
public let thumb960w: String?
public let thumb960h: String?
public let thumb1024: String?
public let thumb1024gif: String?
public let thumb1024w: String?
public let thumb1024h: String?
public let permalink: String?
public let editLink: String?
public let preview: String?
@@ -62,9 +76,9 @@ public struct File {
internal(set) public var isStarred: Bool?
internal(set) public var pinnedTo: [String]?
internal(set) public var comments = [String: Comment]()
internal(set) public var reactions = [String: Reaction]()
internal(set) public var reactions = [Reaction]()
init?(file:[String: AnyObject]?) {
public init(file:[String: Any]?) {
id = file?["id"] as? String
created = file?["created"] as? Int
name = file?["name"] as? String
@@ -78,8 +92,6 @@ public struct File {
isExternal = file?["is_external"] as? Bool
externalType = file?["external_type"] as? String
size = file?["size"] as? Int
url = file?["url"] as? String
urlDownload = file?["url_download"] as? String
urlPrivate = file?["url_private"] as? String
urlPrivateDownload = file?["url_private_download"] as? String
thumb64 = file?["thumb_64"] as? String
@@ -88,6 +100,22 @@ public struct File {
thumb360gif = file?["thumb_360_gif"] as? String
thumb360w = file?["thumb_360_w"] as? String
thumb360h = file?["thumb_360_h"] as? String
thumb480 = file?["thumb_480"] as? String
thumb480gif = file?["thumb_480_gif"] as? String
thumb480w = file?["thumb_480_w"] as? String
thumb480h = file?["thumb_480_h"] as? String
thumb720 = file?["thumb_720"] as? String
thumb720gif = file?["thumb_720_gif"] as? String
thumb720w = file?["thumb_720_w"] as? String
thumb720h = file?["thumb_720_h"] as? String
thumb960 = file?["thumb_960"] as? String
thumb960gif = file?["thumb_960_gif"] as? String
thumb960w = file?["thumb_960_w"] as? String
thumb960h = file?["thumb_960_h"] as? String
thumb1024 = file?["thumb_1024"] as? String
thumb1024gif = file?["thumb_1024_gif"] as? String
thumb1024w = file?["thumb_1024_w"] as? String
thumb1024h = file?["thumb_1024_h"] as? String
permalink = file?["permalink"] as? String
editLink = file?["edit_link"] as? String
preview = file?["preview"] as? String
@@ -99,17 +127,14 @@ public struct File {
channels = file?["channels"] as? [String]
groups = file?["groups"] as? [String]
ims = file?["ims"] as? [String]
initialComment = Comment(comment: file?["initial_comment"] as? [String: AnyObject])
initialComment = Comment(comment: file?["initial_comment"] as? [String: Any])
stars = file?["num_stars"] as? Int
isStarred = file?["is_starred"] as? Bool
pinnedTo = file?["pinned_to"] as? [String]
if let reactions = file?["reactions"] as? [[String: AnyObject]] {
self.reactions = Reaction.reactionsFromArray(reactions)
}
reactions = Reaction.reactionsFromArray(file?["reactions"] as? [[String: Any]])
}
init?(id:String?) {
internal init(id:String?) {
self.id = id
created = nil
name = nil
@@ -122,8 +147,6 @@ public struct File {
isExternal = nil
externalType = nil
size = nil
url = nil
urlDownload = nil
urlPrivate = nil
urlPrivateDownload = nil
thumb64 = nil
@@ -132,6 +155,22 @@ public struct File {
thumb360gif = nil
thumb360w = nil
thumb360h = nil
thumb480 = nil
thumb480gif = nil
thumb480w = nil
thumb480h = nil
thumb720 = nil
thumb720gif = nil
thumb720w = nil
thumb720h = nil
thumb960 = nil
thumb960gif = nil
thumb960w = nil
thumb960h = nil
thumb1024 = nil
thumb1024gif = nil
thumb1024w = nil
thumb1024h = nil
permalink = nil
editLink = nil
preview = nil
@@ -140,11 +179,8 @@ public struct File {
linesMore = nil
initialComment = nil
}
public static func ==(lhs: File, rhs: File) -> Bool {
return lhs.id == rhs.id
}
}
extension File: Equatable {}
public func ==(lhs: File, rhs: File) -> Bool {
return lhs.id == rhs.id
}
+43
View File
@@ -0,0 +1,43 @@
//
// History.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
public struct History {
internal(set) public var latest: Date?
internal(set) public var messages = [Message]()
public let hasMore: Bool?
internal init(history: [String: Any]?) {
if let latestStr = history?["latest"] as? String, let latestDouble = Double(latestStr) {
latest = Date(timeIntervalSince1970: TimeInterval(latestDouble))
}
if let msgs = history?["messages"] as? [[String: Any]] {
for message in msgs {
messages.append(Message(dictionary: message))
}
}
hasMore = history?["has_more"] as? Bool
}
}
+59
View File
@@ -0,0 +1,59 @@
//
// Item.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct Item: Equatable {
public let type: String?
public let ts: String?
public let channel: String?
public let message: Message?
public let file: File?
public let comment: Comment?
public let fileCommentID: String?
internal init(item:[String: Any]?) {
type = item?["type"] as? String
ts = item?["ts"] as? String
channel = item?["channel"] as? String
message = Message(dictionary: item?["message"] as? [String: Any])
// Comment and File can come across as Strings or Dictionaries
if let commentDictionary = item?["comment"] as? [String: Any] {
comment = Comment(comment: commentDictionary)
} else {
comment = Comment(id: item?["comment"] as? String)
}
if let fileDictionary = item?["file"] as? [String: Any] {
file = File(file: fileDictionary)
} else {
file = File(id: item?["file"] as? String)
}
fileCommentID = item?["file_comment"] as? String
}
public static func ==(lhs: Item, rhs: Item) -> Bool {
return lhs.type == rhs.type && lhs.channel == rhs.channel && lhs.file == rhs.file && lhs.comment == rhs.comment && lhs.message == rhs.message
}
}
+101
View File
@@ -0,0 +1,101 @@
//
// Message.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public final class Message: Equatable {
public let type = "message"
public let subtype: String?
internal(set) public var ts: String?
public let user: String?
public let channel: String?
internal(set) public var hidden: Bool?
internal(set) public var text: String?
public let botID: String?
public let username: String?
public let icons: [String: Any]?
public let deletedTs: String?
internal(set) var purpose: String?
internal(set) var topic: String?
internal(set) var name: String?
internal(set) var members: [String]?
internal(set) var oldName: String?
public let upload: Bool?
public let itemType: String?
internal(set) public var isStarred: Bool?
internal(set) var pinnedTo: [String]?
public let comment: Comment?
public let file: File?
internal(set) public var reactions = [Reaction]()
internal(set) public var attachments: [Attachment]?
internal(set) public var responseType: ResponseType?
internal(set) public var replaceOriginal: Bool?
internal(set) public var deleteOriginal: Bool?
public init(dictionary: [String: Any]?) {
subtype = dictionary?["subtype"] as? String
ts = dictionary?["ts"] as? String
user = dictionary?["user"] as? String
channel = dictionary?["channel"] as? String
hidden = dictionary?["hidden"] as? Bool
text = dictionary?["text"] as? String
botID = dictionary?["bot_id"] as? String
username = dictionary?["username"] as? String
icons = dictionary?["icons"] as? [String: Any]
deletedTs = dictionary?["deleted_ts"] as? String
purpose = dictionary?["purpose"] as? String
topic = dictionary?["topic"] as? String
name = dictionary?["name"] as? String
members = dictionary?["members"] as? [String]
oldName = dictionary?["old_name"] as? String
upload = dictionary?["upload"] as? Bool
itemType = dictionary?["item_type"] as? String
isStarred = dictionary?["is_starred"] as? Bool
pinnedTo = dictionary?["pinned_to"] as? [String]
comment = Comment(comment: dictionary?["comment"] as? [String: Any])
file = File(file: dictionary?["file"] as? [String: Any])
reactions = Reaction.reactionsFromArray(dictionary?["reactions"] as? [[String: Any]])
attachments = (dictionary?["attachments"] as? [[String: Any]])?.map{Attachment(attachment: $0)}
responseType = ResponseType(rawValue: dictionary?["response_type"] as? String ?? "")
replaceOriginal = dictionary?["replace_original"] as? Bool
deleteOriginal = dictionary?["delete_original"] as? Bool
}
internal init(ts:String?) {
self.ts = ts
subtype = nil
user = nil
channel = nil
botID = nil
username = nil
icons = nil
deletedTs = nil
upload = nil
itemType = nil
comment = nil
file = nil
}
public static func ==(lhs: Message, rhs: Message) -> Bool {
return lhs.ts == rhs.ts && lhs.user == rhs.user && lhs.text == rhs.text
}
}
+55
View File
@@ -0,0 +1,55 @@
//
// Reaction.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct Reaction: Equatable {
public let name: String?
internal(set) public var user: String?
internal init(reaction:[String: Any]?) {
name = reaction?["name"] as? String
}
internal init(name: String, user: String) {
self.name = name
self.user = user
}
static func reactionsFromArray(_ array: [[String: Any]]?) -> [Reaction] {
var reactions = [Reaction]()
if let array = array {
for reaction in array {
if let users = reaction["users"] as? [String], let name = reaction["name"] as? String {
for user in users {
reactions.append(Reaction(name: name, user: user))
}
}
}
}
return reactions
}
public static func ==(lhs: Reaction, rhs: Reaction) -> Bool {
return lhs.name == rhs.name
}
}
@@ -0,0 +1,51 @@
//
// MessageActionRequest.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal struct MessageActionRequest: Request {
let action: Action?
let callbackID: String?
let team: Team?
let channel: Channel?
let user: User?
let actionTS: String?
let messageTS: String?
let attachmentID: String?
let token: String?
let originalMessage: Message?
let responseURL: String
init(response: [String: Any]?) {
action = (response?["actions"] as? [[String:Any]])?.map({Action(action: $0)}).first
callbackID = response?["callback_id"] as? String
team = Team(team: response?["team"] as? [String: Any])
channel = Channel(channel: response?["channel"] as? [String: Any])
user = User(user: response?["channel"] as? [String: Any])
actionTS = response?["action_ts"] as? String
messageTS = response?["message_ts"] as? String
attachmentID = response?["attachment_id"] as? String
token = response?["token"] as? String
originalMessage = Message(dictionary: response?["original_message"] as? [String: Any])
responseURL = response?["response_url"] as? String ?? ""
}
}
@@ -0,0 +1,43 @@
//
// Response.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct Response {
let text: String
let responseType: ResponseType?
let attachments: [Attachment]?
public init(text: String, responseType: ResponseType? = nil, attachments: [Attachment]? = nil) {
self.responseType = responseType
self.text = text
self.attachments = attachments
}
internal var json: [String: Any] {
var json = [String : Any]()
json["text"] = text
json["response_type"] = responseType?.rawValue
json["attachments"] = attachments?.map({$0.dictionary})
return json
}
}
@@ -0,0 +1,49 @@
//
// WebhookRequest.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
internal struct WebhookRequest: Request {
let token: String?
let teamID: String
let teamDomain: String
let channelID: String
let channelName: String
let userID: String
let userName: String
let command: String
let text: String
let responseURL: String
init(request: [String: Any]?) {
token = request?["token"] as? String
teamID = request?["team_id"] as? String ?? ""
teamDomain = request?["team_domain"] as? String ?? ""
channelID = request?["channel_id"] as? String ?? ""
channelName = request?["channel_name"] as? String ?? ""
userID = request?["user_id"] as? String ?? ""
userName = request?["user_name"] as? String ?? ""
command = request?["command"] as? String ?? ""
text = request?["text"] as? String ?? ""
responseURL = request?["response_url"] as? String ?? ""
}
}
+121
View File
@@ -0,0 +1,121 @@
//
// SlackError.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public enum SlackError: String, Error {
case accountInactive = "account_inactive"
case alreadyArchived = "already_archived"
case alreadyInChannel = "already_in_channel"
case alreadyPinned = "already_pinned"
case alreadyReacted = "already_reacted"
case alreadyStarred = "already_starred"
case badClientSecret = "bad_client_secret"
case badRedirectURI = "bad_redirect_uri"
case badTimeStamp = "bad_timestamp"
case cantArchiveGeneral = "cant_archive_general"
case cantDelete = "cant_delete"
case cantDeleteFile = "cant_delete_file"
case cantDeleteMessage = "cant_delete_message"
case cantInvite = "cant_invite"
case cantInviteSelf = "cant_invite_self"
case cantKickFromGeneral = "cant_kick_from_general"
case cantKickFromLastChannel = "cant_kick_from_last_channel"
case cantKickSelf = "cant_kick_self"
case cantLeaveGeneral = "cant_leave_general"
case cantLeaveLastChannel = "cant_leave_last_channel"
case cantUpdateMessage = "cant_update_message"
case channelNotFound = "channel_not_found"
case complianceExportsPreventDeletion = "compliance_exports_prevent_deletion"
case editWindowClosed = "edit_window_closed"
case fileCommentNotFound = "file_comment_not_found"
case fileDeleted = "file_deleted"
case fileNotFound = "file_not_found"
case fileNotShared = "file_not_shared"
case groupContainsOthers = "group_contains_others"
case invalidArgName = "invalid_arg_name"
case invalidArrayArg = "invalid_array_arg"
case invalidAuth = "invalid_auth"
case invalidChannel = "invalid_channel"
case invalidCharSet = "invalid_charset"
case invalidClientID = "invalid_client_id"
case invalidCode = "invalid_code"
case invalidFormData = "invalid_form_data"
case invalidName = "invalid_name"
case invalidPostType = "invalid_post_type"
case invalidPresence = "invalid_presence"
case invalidTS = "invalid_timestamp"
case invalidTSLatest = "invalid_ts_latest"
case invalidTSOldest = "invalid_ts_oldest"
case isArchived = "is_archived"
case lastMember = "last_member"
case lastRAChannel = "last_ra_channel"
case messageNotFound = "message_not_found"
case messageTooLong = "msg_too_long"
case migrationInProgress = "migration_in_progress"
case missingDuration = "missing_duration"
case missingPostType = "missing_post_type"
case missingScope = "missing_scope"
case nameTaken = "name_taken"
case noChannel = "no_channel"
case noComment = "no_comment"
case noItemSpecified = "no_item_specified"
case noReaction = "no_reaction"
case noText = "no_text"
case notArchived = "not_archived"
case notAuthed = "not_authed"
case notEnoughUsers = "not_enough_users"
case notInChannel = "not_in_channel"
case notInGroup = "not_in_group"
case notPinned = "not_pinned"
case notStarred = "not_starred"
case overPaginationLimit = "over_pagination_limit"
case paidOnly = "paid_only"
case permissionDenied = "perimssion_denied"
case postingToGeneralChannelDenied = "posting_to_general_channel_denied"
case rateLimited = "rate_limited"
case requestTimeout = "request_timeout"
case restrictedAction = "restricted_action"
case snoozeEndFailed = "snooze_end_failed"
case snoozeFailed = "snooze_failed"
case snoozeNotActive = "snooze_not_active"
case tooLong = "too_long"
case tooManyEmoji = "too_many_emoji"
case tooManyReactions = "too_many_reactions"
case tooManyUsers = "too_many_users"
case unknownError
case unknownType = "unknown_type"
case userDisabled = "user_disabled"
case userDoesNotOwnChannel = "user_does_not_own_channel"
case userIsBot = "user_is_bot"
case userIsRestricted = "user_is_restricted"
case userIsUltraRestricted = "user_is_ultra_restricted"
case userListNotSupplied = "user_list_not_supplied"
case userNotFound = "user_not_found"
case userNotVisible = "user_not_visible"
// Client
case clientNetworkError
case clientJSONError
case clientOAuthError
// HTTP
case tooManyRequests
case unknownHTTPError
}
@@ -23,23 +23,25 @@
public struct Team {
public let id: String
public let id: String?
internal(set) public var name: String?
internal(set) public var emailDomain: String?
internal(set) public var domain: String?
internal(set) public var emailDomain: String?
internal(set) public var messageEditWindowMinutes: Int?
internal(set) public var overStorageLimit: Bool?
internal(set) public var prefs: [String: AnyObject]?
internal(set) public var prefs: [String: Any]?
internal(set) public var plan: String?
internal(set) public var icon: TeamIcon?
internal init?(team: [String: AnyObject]?) {
id = team?["id"] as! String
name = team?["id"] as? String
emailDomain = team?["email_domain"] as? String
internal init(team: [String: Any]?) {
id = team?["id"] as? String
name = team?["name"] as? String
domain = team?["domain"] as? String
messageEditWindowMinutes = team?["mesg_edit_window_mins"] as? Int
emailDomain = team?["email_domain"] as? String
messageEditWindowMinutes = team?["msg_edit_window_mins"] as? Int
overStorageLimit = team?["over_storage_limit"] as? Bool
prefs = team?["prefs"] as? [String: AnyObject]
prefs = team?["prefs"] as? [String: Any]
plan = team?["plan"] as? String
icon = TeamIcon(icon: team?["icon"] as? [String: Any])
}
}
+45
View File
@@ -0,0 +1,45 @@
//
// TeamIcon.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct TeamIcon {
internal(set) public var image34: String?
internal(set) public var image44: String?
internal(set) public var image68: String?
internal(set) public var image88: String?
internal(set) public var image102: String?
internal(set) public var image132: String?
internal(set) public var imageOriginal: String?
internal(set) public var imageDefault: Bool?
internal init(icon: [String: Any]?) {
image34 = icon?["image_34"] as? String
image44 = icon?["image_44"] as? String
image68 = icon?["image_68"] as? String
image88 = icon?["image_88"] as? String
image102 = icon?["image_102"] as? String
image132 = icon?["image_132"] as? String
imageOriginal = icon?["image_original"] as? String
imageDefault = icon?["image_default"] as? Bool
}
}
+35
View File
@@ -0,0 +1,35 @@
//
// Topic.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct Topic {
public let value: String?
public let creator: String?
public let lastSet: Int?
internal init(topic: [String: Any]?) {
value = topic?["value"] as? String
creator = topic?["creator"] as? String
lastSet = topic?["last_set"] as? Int
}
}
@@ -24,6 +24,7 @@
public struct User {
public struct Profile {
internal(set) public var firstName: String?
internal(set) public var lastName: String?
internal(set) public var realName: String?
@@ -35,8 +36,9 @@ public struct User {
internal(set) public var image48: String?
internal(set) public var image72: String?
internal(set) public var image192: String?
internal(set) public var customProfile: CustomProfile?
internal init?(profile: [String: AnyObject]?) {
internal init(profile: [String: Any]?) {
firstName = profile?["first_name"] as? String
lastName = profile?["last_name"] as? String
realName = profile?["real_name"] as? String
@@ -48,6 +50,7 @@ public struct User {
image48 = profile?["image_48"] as? String
image72 = profile?["image_72"] as? String
image192 = profile?["image_192"] as? String
customProfile = CustomProfile(customFields: profile?["fields"] as? [String: Any])
}
}
@@ -70,15 +73,15 @@ public struct User {
internal(set) public var timeZone: String?
internal(set) public var timeZoneLabel: String?
internal(set) public var timeZoneOffSet: Int?
internal(set) public var preferences: [String: AnyObject]?
// Client use
internal(set) public var preferences: [String: Any]?
// Client properties
internal(set) public var userGroups: [String: String]?
internal init?(user: [String: AnyObject]?) {
internal init(user: [String: Any]?) {
id = user?["id"] as? String
name = user?["name"] as? String
deleted = user?["deleted"] as? Bool
profile = Profile(profile: user?["profile"] as? [String: AnyObject])
profile = Profile(profile: user?["profile"] as? [String: Any])
color = user?["color"] as? String
isAdmin = user?["is_admin"] as? Bool
isOwner = user?["is_owner"] as? Bool
@@ -93,10 +96,10 @@ public struct User {
timeZone = user?["tz"] as? String
timeZoneLabel = user?["tz_label"] as? String
timeZoneOffSet = user?["tz_offset"] as? Int
preferences = user?["prefs"] as? [String: AnyObject]
preferences = user?["prefs"] as? [String: Any]
}
internal init?(id: String?) {
internal init(id: String?) {
self.id = id
self.isBot = nil
}
@@ -21,11 +21,9 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
public struct UserGroup {
public let id: String?
internal(set) public var teamID: String?
public let isUserGroup: Bool?
internal(set) public var name: String?
@@ -39,11 +37,11 @@ public struct UserGroup {
public let createdBy: String?
internal(set) public var updatedBy: String?
internal(set) public var deletedBy: String?
internal(set) public var preferences: [String: AnyObject]?
internal(set) public var preferences: [String: Any]?
internal(set) public var users: [String]?
internal(set) public var userCount: Int?
internal init?(userGroup: [String: AnyObject]?) {
internal init(userGroup: [String: Any]?) {
id = userGroup?["id"] as? String
teamID = userGroup?["team_id"] as? String
isUserGroup = userGroup?["is_usergroup"] as? Bool
@@ -58,11 +56,10 @@ public struct UserGroup {
createdBy = userGroup?["created_by"] as? String
updatedBy = userGroup?["updated_by"] as? String
deletedBy = userGroup?["deleted_by"] as? String
preferences = userGroup?["prefs"] as? [String: AnyObject]
preferences = userGroup?["prefs"] as? [String: Any]
users = userGroup?["users"] as? [String]
if let count = userGroup?["user_count"] as? String {
userCount = Int(count)
}
}
}
+156
View File
@@ -0,0 +1,156 @@
//
// NetworkInterface.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
internal struct NetworkInterface {
private let apiUrl = "https://slack.com/api/"
internal func request(_ endpoint: Endpoint, parameters: [String: Any?], successClosure: @escaping ([String: Any])->Void, errorClosure: @escaping (SlackError)->Void) {
var components = URLComponents(string: "\(apiUrl)\(endpoint.rawValue)")
if parameters.count > 0 {
components?.queryItems = filterNilParameters(parameters).map { URLQueryItem(name: $0.0, value: "\($0.1)") }
}
guard let url = components?.url else {
errorClosure(SlackError.clientNetworkError)
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {(data, response, internalError) in
do {
successClosure(try self.handleResponse(data, response: response, internalError: internalError))
} catch let error {
errorClosure(error as? SlackError ?? SlackError.unknownError)
}
}.resume()
}
internal func customRequest(_ url: String, data: Data, success: @escaping (Bool)->Void, errorClosure: @escaping (SlackError)->Void) {
guard let string = url.removingPercentEncoding, let url = URL(string: string) else {
errorClosure(SlackError.clientNetworkError)
return
}
var request = URLRequest(url:url)
request.httpMethod = "POST"
let contentType = "application/json"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
request.httpBody = data
URLSession.shared.dataTask(with: request) {(data, response, internalError) in
if internalError == nil {
success(true)
} else {
errorClosure(SlackError.clientNetworkError)
}
}.resume()
}
internal func uploadRequest(data: Data, parameters: [String: Any?], successClosure: @escaping ([String: Any])->Void, errorClosure: @escaping (SlackError)->Void) {
var components = URLComponents(string: "\(apiUrl)\(Endpoint.filesUpload.rawValue)")
if parameters.count > 0 {
components?.queryItems = filterNilParameters(parameters).map { URLQueryItem(name: $0.0, value: "\($0.1)") }
}
guard let url = components?.url else {
errorClosure(SlackError.clientNetworkError)
return
}
var request = URLRequest(url:url)
request.httpMethod = "POST"
let boundaryConstant = randomBoundary()
let contentType = "multipart/form-data; boundary=" + boundaryConstant
let boundaryStart = "--\(boundaryConstant)\r\n"
let boundaryEnd = "--\(boundaryConstant)--\r\n"
let contentDispositionString = "Content-Disposition: form-data; name=\"file\"; filename=\"\(parameters["filename"])\"\r\n"
let contentTypeString = "Content-Type: \(parameters["filetype"])\r\n\r\n"
var requestBodyData: Data = Data()
requestBodyData.append(boundaryStart.data(using: String.Encoding.utf8)!)
requestBodyData.append(contentDispositionString.data(using: String.Encoding.utf8)!)
requestBodyData.append(contentTypeString.data(using: String.Encoding.utf8)!)
requestBodyData.append(data)
requestBodyData.append("\r\n".data(using: String.Encoding.utf8)!)
requestBodyData.append(boundaryEnd.data(using: String.Encoding.utf8)!)
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
request.httpBody = requestBodyData as Data
URLSession.shared.dataTask(with: request) {(data, response, internalError) in
do {
successClosure(try self.handleResponse(data, response: response, internalError: internalError))
} catch let error {
errorClosure(error as? SlackError ?? SlackError.unknownError)
}
}.resume()
}
private func handleResponse(_ data: Data?, response:URLResponse?, internalError:Error?) throws -> [String: Any] {
guard let data = data, let response = response as? HTTPURLResponse else {
throw SlackError.clientNetworkError
}
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
throw SlackError.clientJSONError
}
switch response.statusCode {
case 200:
if (json["ok"] as! Bool == true) {
return json
} else {
if let errorString = json["error"] as? String {
throw SlackError(rawValue: errorString) ?? .unknownError
} else {
throw SlackError.unknownError
}
}
case 429:
throw SlackError.tooManyRequests
default:
throw SlackError.clientNetworkError
}
} catch let error {
if let slackError = error as? SlackError {
throw slackError
} else {
throw SlackError.unknownError
}
}
}
private func randomBoundary() -> String {
return String(format: "slackkit.boundary.%08x%08x", arc4random(), arc4random())
}
//MARK: - Filter Nil Parameters
private func filterNilParameters(_ parameters: [String: Any?]) -> [String: Any] {
var finalParameters = [String: Any]()
for (key, value) in parameters {
if let unwrapped = value {
finalParameters[key] = unwrapped
}
}
return finalParameters
}
}
+97
View File
@@ -0,0 +1,97 @@
//
// OAuthServer.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import Swifter
internal protocol OAuthDelegate {
func userAuthed(_ response: OAuthResponse)
}
public struct OAuthServer {
private let oauthURL = "https://slack.com/oauth/authorize"
private let http = HttpServer()
private let clientID: String
private let clientSecret: String
private let state: String?
private let redirectURI: String?
private var delegate: OAuthDelegate?
internal init(clientID: String, clientSecret: String, state: String? = nil, redirectURI: String? = nil, port:in_port_t = 8080, forceIPV4: Bool = false, delegate: OAuthDelegate? = nil) throws {
self.clientID = clientID
self.clientSecret = clientSecret
self.state = state ?? "state"
self.redirectURI = redirectURI
self.delegate = delegate
oauthRoute()
start(port, forceIPV4: forceIPV4)
}
public func start(_ port: in_port_t = 8080, forceIPV4: Bool = false) {
do {
try http.start(port, forceIPv4: forceIPV4)
} catch let error as NSError {
print("Server failed to start with error: \(error)")
}
}
public func stop() {
http.stop()
}
private func oauthRoute() {
http["/oauth"] = { request in
guard let response = AuthorizeResponse(queryParameters: request.queryParams), response.state == self.state else {
return .badRequest(.text("Bad request."))
}
WebAPI.oauthAccess(clientID: self.clientID, clientSecret: self.clientSecret, code: response.code, redirectURI: self.redirectURI, success: {(response) in
self.delegate?.userAuthed(OAuthResponse(response: response))
}, failure: {(error) in
print("Authorization failed")
})
if let redirect = self.redirectURI {
return .movedPermanently(redirect)
}
return .ok(.text("Authentication successful."))
}
}
private func oauthURLRequest(_ authorize: AuthorizeRequest) -> URLRequest? {
var components = URLComponents(string: "\(oauthURL)")
components?.queryItems = [
URLQueryItem(name: "client_id", value: "\(authorize.clientID)"),
URLQueryItem(name: "scope", value: "\(authorize.scope)"),
]
guard let url = components?.url else {
return nil
}
return URLRequest(url: url)
}
public func authorizeRequest(_ scope:[Scope], redirectURI: String, state: String = "slackkit", team: String? = nil) -> URLRequest? {
let request = AuthorizeRequest(clientID: clientID, scope: scope, redirectURI: redirectURI, state: state, team: team)
return oauthURLRequest(request)
}
}
+90
View File
@@ -0,0 +1,90 @@
//
// Server.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import Swifter
internal enum Reply {
case json(response: Response)
case text(body: String)
case badRequest
}
internal protocol Request {
var responseURL: String { get }
}
open class Server {
internal let http = HttpServer()
internal let token: String
internal init(token: String) {
self.token = token
}
open func start(_ port: in_port_t = 8080, forceIPV4: Bool = false) {
do {
try http.start(port, forceIPv4: forceIPV4)
} catch let error as NSError {
print("Server failed to start with error: \(error)")
}
}
open func stop() {
http.stop()
}
internal func request(_ request:Request, reply: Reply) -> HttpResponse {
switch reply {
case .text(let body):
return .ok(.text(body))
case .json(let response):
return .ok(.json(response.json as AnyObject))
case .badRequest:
return .badRequest(.text("Bad request."))
}
}
internal func dictionaryFromRequest(_ body: [UInt8]) -> [String: Any]? {
let string = String(data: Data(bytes: UnsafePointer<UInt8>(body), count: body.count), encoding: String.Encoding.utf8)
if let body = string?.components(separatedBy: "&") {
var dict: [String: Any] = [:]
for argument in body {
let kv = argument.components(separatedBy: "=")
if let key = kv.first, let value = kv.last {
dict[key] = value as Any?
}
}
return dict
}
return nil
}
internal func jsonFromRequest(_ string: String) -> [String: Any]? {
guard let data = string.data(using: String.Encoding.utf8) else {
return nil
}
return (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] ?? nil
}
}
+66
View File
@@ -0,0 +1,66 @@
//
// SlackKit.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
public final class SlackKit: OAuthDelegate {
internal(set) public var oauth: OAuthServer?
internal(set) public var clients: [String: Client] = [:]
private let clientOptions: ClientOptions
// Initalization block
public var onClientInitalization: ((Client) -> Void)?
// If you already have an API token
public init(withAPIToken token: String, clientOptions: ClientOptions = ClientOptions()) {
self.clientOptions = clientOptions
let client = Client(apiToken: token)
DispatchQueue.main.async(execute: {
self.onClientInitalization?(client)
})
clients[token] = client
client.connect(options: self.clientOptions)
}
// If you're going to be receiving and/or initiating OAuth requests, provide a client ID and secret
public init(clientID: String, clientSecret: String, state: String? = nil, redirectURI: String? = nil, port:in_port_t = 8080, forceIPV4: Bool = false, clientOptions: ClientOptions = ClientOptions()) {
self.clientOptions = clientOptions
oauth = try? OAuthServer(clientID: clientID, clientSecret: clientSecret, state: state, redirectURI: redirectURI, port: port, forceIPV4: forceIPV4, delegate: self)
}
internal func userAuthed(_ response: OAuthResponse) {
// User auth
if let token = response.accessToken {
let client = Client(apiToken: token)
self.onClientInitalization?(client)
clients[token] = client
}
// Bot User
if let token = response.bot?.botToken {
let client = Client(apiToken: token)
self.onClientInitalization?(client)
clients[token] = client
client.connect(options: self.clientOptions)
}
}
}
+707
View File
@@ -0,0 +1,707 @@
//
// WebAPI.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
internal enum Endpoint: String {
case apiTest = "api.test"
case authRevoke = "auth.revoke"
case authTest = "auth.test"
case channelsHistory = "channels.history"
case channelsInfo = "channels.info"
case channelsList = "channels.list"
case channelsMark = "channels.mark"
case channelsSetPurpose = "channels.setPurpose"
case channelsSetTopic = "channels.setTopic"
case chatDelete = "chat.delete"
case chatPostMessage = "chat.postMessage"
case chatMeMessage = "chat.meMessage"
case chatUpdate = "chat.update"
case dndInfo = "dnd.info"
case dndTeamInfo = "dnd.teamInfo"
case emojiList = "emoji.list"
case filesCommentsAdd = "files.comments.add"
case filesCommentsEdit = "files.comments.edit"
case filesCommentsDelete = "files.comments.delete"
case filesDelete = "files.delete"
case filesInfo = "files.info"
case filesUpload = "files.upload"
case groupsClose = "groups.close"
case groupsHistory = "groups.history"
case groupsInfo = "groups.info"
case groupsList = "groups.list"
case groupsMark = "groups.mark"
case groupsOpen = "groups.open"
case groupsSetPurpose = "groups.setPurpose"
case groupsSetTopic = "groups.setTopic"
case imClose = "im.close"
case imHistory = "im.history"
case imList = "im.list"
case imMark = "im.mark"
case imOpen = "im.open"
case mpimClose = "mpim.close"
case mpimHistory = "mpim.history"
case mpimList = "mpim.list"
case mpimMark = "mpim.mark"
case mpimOpen = "mpim.open"
case oauthAccess = "oauth.access"
case pinsAdd = "pins.add"
case pinsRemove = "pins.remove"
case reactionsAdd = "reactions.add"
case reactionsGet = "reactions.get"
case reactionsList = "reactions.list"
case reactionsRemove = "reactions.remove"
case rtmStart = "rtm.start"
case starsAdd = "stars.add"
case starsRemove = "stars.remove"
case teamInfo = "team.info"
case usersGetPresence = "users.getPresence"
case usersInfo = "users.info"
case usersList = "users.list"
case usersSetActive = "users.setActive"
case usersSetPresence = "users.setPresence"
}
public final class WebAPI {
public typealias FailureClosure = (_ error: SlackError)->Void
public enum InfoType: String {
case purpose = "purpose"
case topic = "topic"
}
public enum ParseMode: String {
case full = "full"
case none = "none"
}
public enum Presence: String {
case auto = "auto"
case away = "away"
}
private enum ChannelType: String {
case channel = "channel"
case group = "group"
case im = "im"
}
private let networkInterface: NetworkInterface
private let token: String
public init(token: String) {
self.networkInterface = NetworkInterface()
self.token = token
}
//MARK: - RTM
public func rtmStart(simpleLatest: Bool? = nil, noUnreads: Bool? = nil, mpimAware: Bool? = nil, success: ((_ response: [String: Any])->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "simple_latest": simpleLatest, "no_unreads": noUnreads, "mpim_aware": mpimAware]
networkInterface.request(.rtmStart, parameters: parameters, successClosure: {(response) in
success?(response)
}) {(error) in
failure?(error)
}
}
//MARK: - Auth
public func authenticationTest(success: ((_ authenticated: Bool)->Void)?, failure: FailureClosure?) {
networkInterface.request(.authTest, parameters: ["token": token], successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
public static func oauthAccess(clientID: String, clientSecret: String, code: String, redirectURI: String? = nil, success: ((_ response: [String: Any])->Void)?, failure: ((SlackError)->Void)?) {
let parameters: [String: Any?] = ["client_id": clientID, "client_secret": clientSecret, "code": code, "redirect_uri": redirectURI]
NetworkInterface().request(.oauthAccess, parameters: parameters, successClosure: {(response) in
success?(response)
}) {(error) in
failure?(error)
}
}
public static func oauthRevoke(token: String, test: Bool? = nil, success: ((_ revoked:Bool)->Void)?, failure: ((SlackError)->Void)?) {
let parameters: [String: Any?] = ["token": token, "test": test]
NetworkInterface().request(.authRevoke, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
//MARK: - Channels
public func channelHistory(id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((_ history: History)->Void)?, failure: FailureClosure?) {
history(.channelsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {(history) in
success?(history)
}) {(error) in
failure?(error)
}
}
public func channelInfo(id: String, success: ((_ channel: Channel)->Void)?, failure: FailureClosure?) {
info(.channelsInfo, type:.channel, id: id, success: {(channel) in
success?(channel)
}) {(error) in
failure?(error)
}
}
public func channelsList(excludeArchived: Bool = false, success: ((_ channels: [[String: Any]]?)->Void)?, failure: FailureClosure?) {
list(.channelsList, type:.channel, excludeArchived: excludeArchived, success: {(channels) in
success?(channels)
}) {(error) in
failure?(error)
}
}
public func markChannel(channel: String, timestamp: String, success: ((_ ts: String)->Void)?, failure: FailureClosure?) {
mark(.channelsMark, channel: channel, timestamp: timestamp, success: {(ts) in
success?(timestamp)
}) {(error) in
failure?(error)
}
}
public func setChannelPurpose(channel: String, purpose: String, success: ((_ purposeSet: Bool)->Void)?, failure: FailureClosure?) {
setInfo(.channelsSetPurpose, type: .purpose, channel: channel, text: purpose, success: {(purposeSet) in
success?(purposeSet)
}) {(error) in
failure?(error)
}
}
public func setChannelTopic(channel: String, topic: String, success: ((_ topicSet: Bool)->Void)?, failure: FailureClosure?) {
setInfo(.channelsSetTopic, type: .topic, channel: channel, text: topic, success: {(topicSet) in
success?(topicSet)
}) {(error) in
failure?(error)
}
}
//MARK: - Messaging
public func deleteMessage(channel: String, ts: String, success: ((_ deleted: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "channel": channel, "ts": ts]
networkInterface.request(.chatDelete, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
public func sendMessage(channel: String, text: String, username: String? = nil, asUser: Bool? = nil, parse: ParseMode? = nil, linkNames: Bool? = nil, attachments: [Attachment?]? = nil, unfurlLinks: Bool? = nil, unfurlMedia: Bool? = nil, iconURL: String? = nil, iconEmoji: String? = nil, success: (((ts: String?, channel: String?))->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "channel": channel, "text": text.slackFormatEscaping, "as_user": asUser, "parse": parse?.rawValue, "link_names": linkNames, "unfurl_links": unfurlLinks, "unfurlMedia": unfurlMedia, "username": username, "icon_url": iconURL, "icon_emoji": iconEmoji, "attachments": encodeAttachments(attachments)]
networkInterface.request(.chatPostMessage, parameters: parameters, successClosure: {(response) in
success?((ts: response["ts"] as? String, response["channel"] as? String))
}) {(error) in
failure?(error)
}
}
public func sendMeMessage(channel: String, text: String, success: (((ts: String?, channel: String?))->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "channel": channel, "text": text.slackFormatEscaping]
networkInterface.request(.chatMeMessage, parameters: parameters, successClosure: {(response) in
success?((ts: response["ts"] as? String, response["channel"] as? String))
}) {(error) in
failure?(error)
}
}
public func updateMessage(channel: String, ts: String, message: String, attachments: [Attachment?]? = nil, parse:ParseMode = .none, linkNames: Bool = false, success: ((_ updated: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "channel": channel, "ts": ts, "text": message.slackFormatEscaping, "parse": parse.rawValue, "link_names": linkNames, "attachments": encodeAttachments(attachments)]
networkInterface.request(.chatUpdate, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
//MARK: - Do Not Disturb
public func dndInfo(user: String? = nil, success: ((_ status: DoNotDisturbStatus)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "user": user]
networkInterface.request(.dndInfo, parameters: parameters, successClosure: {(response) in
success?(DoNotDisturbStatus(status: response))
}) {(error) in
failure?(error)
}
}
public func dndTeamInfo(users: [String]? = nil, success: ((_ statuses: [String: DoNotDisturbStatus])->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "users": users?.joined(separator: ",")]
networkInterface.request(.dndTeamInfo, parameters: parameters, successClosure: {(response) in
guard let usersDictionary = response["users"] as? [String: Any] else {
success?([:])
return
}
success?(self.enumerateDNDStatuses(usersDictionary))
}) {(error) in
failure?(error)
}
}
//MARK: - Emoji
public func emojiList(success: ((_ emojiList: [String: Any]?)->Void)?, failure: FailureClosure?) {
networkInterface.request(.emojiList, parameters: ["token": token], successClosure: {(response) in
success?(response["emoji"] as? [String: Any])
}) {(error) in
failure?(error)
}
}
//MARK: - Files
public func deleteFile(fileID: String, success: ((_ deleted: Bool)->Void)?, failure: FailureClosure?) {
let parameters = ["token": token, "file": fileID]
networkInterface.request(.filesDelete, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
public func fileInfo(fileID: String, commentCount: Int = 100, totalPages: Int = 1, success: ((_ file: File)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "file": fileID, "count": commentCount, "totalPages": totalPages]
networkInterface.request(.filesInfo, parameters: parameters, successClosure: {(response) in
var file = File(file: response["file"] as? [String: Any])
(response["comments"] as? [[String: Any]])?.forEach { comment in
let comment = Comment(comment: comment)
if let id = comment.id {
file.comments[id] = comment
}
}
success?(file)
}) {(error) in
failure?(error)
}
}
public func uploadFile(file: Data, filename: String, filetype: String = "auto", title: String? = nil, initialComment: String? = nil, channels: [String]? = nil, success: ((_ file: File)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "filename": filename, "filetype": filetype, "title": title, "initial_comment": initialComment, "channels": channels?.joined(separator: ",")]
networkInterface.uploadRequest(data: file, parameters: parameters, successClosure: {(response) in
success?(File(file: response["file"] as? [String: Any]))
}) {(error) in
failure?(error)
}
}
//MARK: - File Comments
public func addFileComment(fileID: String, comment: String, success: ((_ comment: Comment)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "file": fileID, "comment": comment.slackFormatEscaping]
networkInterface.request(.filesCommentsAdd, parameters: parameters, successClosure: {(response) in
success?(Comment(comment: response["comment"] as? [String: Any]))
}) {(error) in
failure?(error)
}
}
public func editFileComment(fileID: String, commentID: String, comment: String, success: ((_ comment: Comment)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "file": fileID, "id": commentID, "comment": comment.slackFormatEscaping]
networkInterface.request(.filesCommentsEdit, parameters: parameters, successClosure: {(response) in
success?(Comment(comment: response["comment"] as? [String: Any]))
}) {(error) in
failure?(error)
}
}
public func deleteFileComment(fileID: String, commentID: String, success: ((_ deleted: Bool?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "file": fileID, "id": commentID]
networkInterface.request(.filesCommentsDelete, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
//MARK: - Groups
public func closeGroup(groupID: String, success: ((_ closed: Bool)->Void)?, failure: FailureClosure?) {
close(.groupsClose, channelID: groupID, success: {(closed) in
success?(closed)
}) {(error) in
failure?(error)
}
}
public func groupHistory(id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((_ history: History)->Void)?, failure: FailureClosure?) {
history(.groupsHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {(history) in
success?(history)
}) {(error) in
failure?(error)
}
}
public func groupInfo(id: String, success: ((_ channel: Channel)->Void)?, failure: FailureClosure?) {
info(.groupsInfo, type:.group, id: id, success: {(channel) in
success?(channel)
}) {(error) in
failure?(error)
}
}
public func groupsList(excludeArchived: Bool = false, success: ((_ channels: [[String: Any]]?)->Void)?, failure: FailureClosure?) {
list(.groupsList, type:.group, excludeArchived: excludeArchived, success: {(channels) in
success?(channels)
}) {(error) in
failure?(error)
}
}
public func markGroup(channel: String, timestamp: String, success: ((_ ts: String)->Void)?, failure: FailureClosure?) {
mark(.groupsMark, channel: channel, timestamp: timestamp, success: {(ts) in
success?(timestamp)
}) {(error) in
failure?(error)
}
}
public func openGroup(channel: String, success: ((_ opened: Bool)->Void)?, failure: FailureClosure?) {
let parameters = ["token": token, "channel":channel]
networkInterface.request(.groupsOpen, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
public func setGroupPurpose(channel: String, purpose: String, success: ((_ purposeSet: Bool)->Void)?, failure: FailureClosure?) {
setInfo(.groupsSetPurpose, type: .purpose, channel: channel, text: purpose, success: {(purposeSet) in
success?(purposeSet)
}) {(error) in
failure?(error)
}
}
public func setGroupTopic(channel: String, topic: String, success: ((_ topicSet: Bool)->Void)?, failure: FailureClosure?) {
setInfo(.groupsSetTopic, type: .topic, channel: channel, text: topic, success: {(topicSet) in
success?(topicSet)
}) {(error) in
failure?(error)
}
}
//MARK: - IM
public func closeIM(channel: String, success: ((_ closed: Bool)->Void)?, failure: FailureClosure?) {
close(.imClose, channelID: channel, success: {(closed) in
success?(closed)
}) {(error) in
failure?(error)
}
}
public func imHistory(id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((_ history: History)->Void)?, failure: FailureClosure?) {
history(.imHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {(history) in
success?(history)
}) {(error) in
failure?(error)
}
}
public func imsList(excludeArchived: Bool = false, success: ((_ channels: [[String: Any]]?)->Void)?, failure: FailureClosure?) {
list(.imList, type:.im, excludeArchived: excludeArchived, success: {(channels) in
success?(channels)
}) {(error) in
failure?(error)
}
}
public func markIM(channel: String, timestamp: String, success: ((_ ts: String)->Void)?, failure: FailureClosure?) {
mark(.imMark, channel: channel, timestamp: timestamp, success: {(ts) in
success?(timestamp)
}) {(error) in
failure?(error)
}
}
public func openIM(userID: String, success: ((_ imID: String?)->Void)?, failure: FailureClosure?) {
let parameters = ["token": token, "user": userID]
networkInterface.request(.imOpen, parameters: parameters, successClosure: {(response) in
let group = response["channel"] as? [String: Any]
success?(group?["id"] as? String)
}) {(error) in
failure?(error)
}
}
//MARK: - MPIM
public func closeMPIM(channel: String, success: ((_ closed: Bool)->Void)?, failure: FailureClosure?) {
close(.mpimClose, channelID: channel, success: {(closed) in
success?(closed)
}) {(error) in
failure?(error)
}
}
public func mpimHistory(id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((_ history: History)->Void)?, failure: FailureClosure?) {
history(.mpimHistory, id: id, latest: latest, oldest: oldest, inclusive: inclusive, count: count, unreads: unreads, success: {(history) in
success?(history)
}) {(error) in
failure?(error)
}
}
public func mpimsList(excludeArchived: Bool = false, success: ((_ channels: [[String: Any]]?)->Void)?, failure: FailureClosure?) {
list(.mpimList, type:.group, excludeArchived: excludeArchived, success: {(channels) in
success?(channels)
}) {(error) in
failure?(error)
}
}
public func markMPIM(channel: String, timestamp: String, success: ((_ ts: String)->Void)?, failure: FailureClosure?) {
mark(.mpimMark, channel: channel, timestamp: timestamp, success: {(ts) in
success?(timestamp)
}) {(error) in
failure?(error)
}
}
public func openMPIM(userIDs: [String], success: ((_ mpimID: String?)->Void)?, failure: FailureClosure?) {
let parameters = ["token": token, "users": userIDs.joined(separator: ",")]
networkInterface.request(.mpimOpen, parameters: parameters, successClosure: {(response) in
let group = response["group"] as? [String: Any]
success?(group?["id"] as? String)
}) {(error) in
failure?(error)
}
}
//MARK: - Pins
public func pinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((_ pinned: Bool)->Void)?, failure: FailureClosure?) {
pin(.pinsAdd, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: {(ok) in
success?(ok)
}) {(error) in
failure?(error)
}
}
public func unpinItem(channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((_ unpinned: Bool)->Void)?, failure: FailureClosure?) {
pin(.pinsRemove, channel: channel, file: file, fileComment: fileComment, timestamp: timestamp, success: {(ok) in
success?(ok)
}) {(error) in
failure?(error)
}
}
private func pin(_ endpoint: Endpoint, channel: String, file: String? = nil, fileComment: String? = nil, timestamp: String? = nil, success: ((_ ok: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "channel": channel, "file": file, "file_comment": fileComment, "timestamp": timestamp]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
//MARK: - Reactions
// One of file, file_comment, or the combination of channel and timestamp must be specified.
public func addReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((_ reacted: Bool)->Void)?, failure: FailureClosure?) {
react(.reactionsAdd, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {(ok) in
success?(ok)
}) {(error) in
failure?(error)
}
}
// One of file, file_comment, or the combination of channel and timestamp must be specified.
public func removeReaction(name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((_ unreacted: Bool)->Void)?, failure: FailureClosure?) {
react(.reactionsRemove, name: name, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {(ok) in
success?(ok)
}) {(error) in
failure?(error)
}
}
private func react(_ endpoint: Endpoint, name: String, file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((_ ok: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "name": name, "file": file, "file_comment": fileComment, "channel": channel, "timestamp": timestamp]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
//MARK: - Stars
// One of file, file_comment, channel, or the combination of channel and timestamp must be specified.
public func addStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((_ starred: Bool)->Void)?, failure: FailureClosure?) {
star(.starsAdd, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {(ok) in
success?(ok)
}) {(error) in
failure?(error)
}
}
// One of file, file_comment, channel, or the combination of channel and timestamp must be specified.
public func removeStar(file: String? = nil, fileComment: String? = nil, channel: String? = nil, timestamp: String? = nil, success: ((_ unstarred: Bool)->Void)?, failure: FailureClosure?) {
star(.starsRemove, file: file, fileComment: fileComment, channel: channel, timestamp: timestamp, success: {(ok) in
success?(ok)
}) {(error) in
failure?(error)
}
}
private func star(_ endpoint: Endpoint, file: String?, fileComment: String?, channel: String?, timestamp: String?, success: ((_ ok: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any?] = ["token": token, "file": file, "file_comment": fileComment, "channel": channel, "timestamp": timestamp]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
//MARK: - Team
public func teamInfo(success: ((_ info: [String: Any]?)->Void)?, failure: FailureClosure?) {
networkInterface.request(.teamInfo, parameters: ["token": token], successClosure: {(response) in
success?(response["team"] as? [String: Any])
}) {(error) in
failure?(error)
}
}
//MARK: - Users
public func userPresence(user: String, success: ((_ presence: String?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "user": user]
networkInterface.request(.usersGetPresence, parameters: parameters, successClosure: {(response) in
success?(response["presence"] as? String)
}) {(error) in
failure?(error)
}
}
public func userInfo(id: String, success: ((_ user: User)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "user": id]
networkInterface.request(.usersInfo, parameters: parameters, successClosure: {(response) in
success?(User(user: response["user"] as? [String: Any]))
}) {(error) in
failure?(error)
}
}
public func usersList(includePresence: Bool = false, success: ((_ userList: [[String: Any]]?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "presence": includePresence]
networkInterface.request(.usersList, parameters: parameters, successClosure: {(response) in
success?(response["members"] as? [[String: Any]])
}) {(error) in
failure?(error)
}
}
public func setUserActive(success: ((_ success: Bool)->Void)?, failure: FailureClosure?) {
networkInterface.request(.usersSetActive, parameters: ["token": token], successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
public func setUserPresence(presence: Presence, success: ((_ success: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "presence": presence.rawValue]
networkInterface.request(.usersSetPresence, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
//MARK: - Channel Utilities
private func close(_ endpoint: Endpoint, channelID: String, success: ((_ closed: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "channel": channelID]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
private func history(_ endpoint: Endpoint, id: String, latest: String = "\(Date().timeIntervalSince1970)", oldest: String = "0", inclusive: Bool = false, count: Int = 100, unreads: Bool = false, success: ((_ history: History)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "channel": id, "latest": latest, "oldest": oldest, "inclusive": inclusive, "count": count, "unreads": unreads]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(History(history: response))
}) {(error) in
failure?(error)
}
}
private func info(_ endpoint: Endpoint, type: ChannelType, id: String, success: ((_ channel: Channel)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "channel": id]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(Channel(channel: response[type.rawValue] as? [String: Any]))
}) {(error) in
failure?(error)
}
}
private func list(_ endpoint: Endpoint, type: ChannelType, excludeArchived: Bool = false, success: ((_ channels: [[String: Any]]?)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "exclude_archived": excludeArchived]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(response[type.rawValue+"s"] as? [[String: Any]])
}) {(error) in
failure?(error)
}
}
private func mark(_ endpoint: Endpoint, channel: String, timestamp: String, success: ((_ ts: String)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "channel": channel, "ts": timestamp]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(timestamp)
}) {(error) in
failure?(error)
}
}
fileprivate func setInfo(_ endpoint: Endpoint, type: InfoType, channel: String, text: String, success: ((_ success: Bool)->Void)?, failure: FailureClosure?) {
let parameters: [String: Any] = ["token": token, "channel": channel, type.rawValue: text]
networkInterface.request(endpoint, parameters: parameters, successClosure: {(response) in
success?(true)
}) {(error) in
failure?(error)
}
}
//MARK: - Encode Attachments
private func encodeAttachments(_ attachments: [Attachment?]?) -> String? {
if let attachments = attachments {
var attachmentArray: [[String: Any]] = []
for attachment in attachments {
if let attachment = attachment {
attachmentArray.append(attachment.dictionary)
}
}
do {
let data = try JSONSerialization.data(withJSONObject: attachmentArray, options: [])
return String(data: data, encoding: String.Encoding.utf8)
} catch _ {
}
}
return nil
}
//MARK: - Enumerate Do Not Disturb Status
private func enumerateDNDStatuses(_ statuses: [String: Any]) -> [String: DoNotDisturbStatus] {
var retVal = [String: DoNotDisturbStatus]()
for key in statuses.keys {
retVal[key] = DoNotDisturbStatus(status: statuses[key] as? [String: Any])
}
return retVal
}
}
+49
View File
@@ -0,0 +1,49 @@
//
// WebhookServer.swift
//
// Copyright © 2016 Peter Zignego. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
open class WebhookServer: Server {
public init(token: String, route: String, response: Response) {
super.init(token: token)
addRoute(route, response: response)
}
open func addRoute(_ route: String, response: Response) {
http["/\(route)"] = { request in
let webhookRequest = WebhookRequest(request: self.dictionaryFromRequest(request.body))
if webhookRequest.token == self.token {
return self.request(webhookRequest, reply: self.replyForResponse(response))
} else {
return .badRequest(.text("Bad request."))
}
}
}
private func replyForResponse(_ response: Response) -> Reply {
if response.attachments == nil && response.responseType == nil {
return Reply.text(body: response.text)
} else {
return Reply.json(response: response)
}
}
}
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.1.11</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Peter Zignego. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.1.11</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 Peter Zignego. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>3.1.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>