slacks 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 043b3057a21f6d84396e6c3734011858829c2384
4
- data.tar.gz: d3fb04c0eb022c732ccec74557d55fc2f96c06dd
3
+ metadata.gz: 3e10c085c812db7ebb4c4882d2d29963c9b29941
4
+ data.tar.gz: 8d4fcc5ce2622251a901d97b35d895819e874fbd
5
5
  SHA512:
6
- metadata.gz: 81126a16a437d183556368bad98e01ee9ffec447a5388e7558eeab448daf424baf1adb58895d03c2f8af8777e96296018bf7b77a8917be4bb18bd4a4c78bc25b
7
- data.tar.gz: 57cfaa77cf77442c22c8433b5241e2a4421f4c7de5a5e7f1275a99f7abcc9b7ecdaa3c5a024764fdbbcc5786080d66e713c7fbb381d639560b278b1152daebbe
6
+ metadata.gz: 3eb4f58e4f8cd344d133539ccf3413ff6fce1622fc3228a61ca77cff35c9ff8cbe25c288d4a4cf7425e99d62802efe4db58f191a01b8c85b32aa20416df63640
7
+ data.tar.gz: f5094c0ede839359afed74c5726df6a2df7731b19317210e73b4f6d4d558653916f7a061a2a77168e377b7d80a98e8126483eca01e5c9d36818739222988613b
data/README.md CHANGED
@@ -2,34 +2,92 @@
2
2
 
3
3
  A library for communicating via Slack
4
4
 
5
- ## Installation
6
5
 
7
- Add this line to your application's Gemfile:
6
+
7
+ ## Usage
8
+
9
+ ##### Speaking
10
+
11
+ Slacks uses [Slack's Web API](https://api.slack.com/web) to allow your bot to communicate on Slack:
8
12
 
9
13
  ```ruby
10
- gem "slacks"
14
+ require "slacks"
15
+ slack = Slacks::Connection.new("xoxb-0123456789-abcdefghijklmnopqrstuvwx")
16
+ slack.send_message "Hi everyone!", channel: "#general"
11
17
  ```
12
18
 
13
- And then execute:
19
+ You can post [attachment](https://api.slack.com/docs/attachments):
14
20
 
15
- $ bundle
21
+ ```ruby
22
+ slack.send_message "", channel: "#general", attachments: [{
23
+ color: "#36a64f",
24
+ fallback: "Tests passed!",
25
+ text: "Tests passed!",
26
+ fields: [{
27
+ title: "Tests",
28
+ value: "143",
29
+ short: true
30
+ }, {
31
+ title: "Assertions",
32
+ value: "298",
33
+ short: true
34
+ }]
35
+ }]
36
+ ```
16
37
 
17
- Or install it yourself as:
38
+ (For more information about sending messages, see [chat.postMessage](https://api.slack.com/methods/chat.postMessage).)
18
39
 
19
- $ gem install slacks
40
+ You can indicate that your bot is typing:
20
41
 
42
+ ```ruby
43
+ slack.typing_on "#general"
44
+ ```
45
+
46
+ You can react to messages:
47
+
48
+ ```ruby
49
+ slack.add_reaction "+1", message.ts
50
+ ```
21
51
 
22
52
 
23
- ## Usage
53
+
54
+ #### Listening
55
+
56
+ Slacks uses [Slack's RTM API](https://api.slack.com/rtm) to allow your bot to listen on Slack. Slacks connects to a websocket provided by Slack and allows you to set up listeners for events by type:
24
57
 
25
58
  ```ruby
26
59
  require "slacks"
27
60
  slack = Slacks::Connection.new("xoxb-0123456789-abcdefghijklmnopqrstuvwx")
28
- slack.listen! do |message|
29
- p message
61
+ slack.on "message" do |event|
62
+ puts "I heard #{event["user"]} say #{event["text"].inspect}"
63
+ end
64
+ slack.on "reaction_added" do |event|
65
+ next unless event["item"]["type"] == "message"
66
+ message = slack.get_message data["item"]["channel"], data["item"]["ts"]
67
+ puts "#{event["user"]} added #{event["reaction"]} to #{message["text"].inspect}"
30
68
  end
69
+ slack.listen!
70
+ ```
71
+
72
+
73
+
74
+ ## Installation
75
+
76
+ Add this line to your application's Gemfile:
77
+
78
+ ```ruby
79
+ gem "slacks"
31
80
  ```
32
81
 
82
+ And then execute:
83
+
84
+ $ bundle
85
+
86
+ Or install it yourself as:
87
+
88
+ $ gem install slacks
89
+
90
+
33
91
 
34
92
 
35
93
  ## Development
@@ -1,9 +1,9 @@
1
1
  require "slacks/bot_user"
2
2
  require "slacks/channel"
3
3
  require "slacks/driver"
4
+ require "slacks/observer"
4
5
  require "slacks/errors"
5
6
  require "slacks/guest_channel"
6
- require "slacks/rtm_event"
7
7
  require "slacks/team"
8
8
  require "slacks/user"
9
9
  require "faraday"
@@ -11,15 +11,11 @@ require "faraday/raise_errors"
11
11
 
12
12
  module Slacks
13
13
  class Connection
14
+ include ::Slacks::Observer
15
+
14
16
  attr_reader :team, :bot, :token
15
17
  attr_accessor :typing_speed
16
18
 
17
- EVENT_MESSAGE = "message".freeze
18
- EVENT_GROUP_JOINED = "group_joined".freeze
19
- EVENT_USER_JOINED = "team_join".freeze
20
- EVENT_REACTION_ADDED = "reaction_added".freeze
21
- EVENT_REACTION_REMOVED = "reaction_removed".freeze
22
-
23
19
  def initialize(token, options={})
24
20
  @token = token
25
21
  @typing_speed = options.fetch(:typing_speed, 100.0)
@@ -87,7 +83,7 @@ module Slacks
87
83
 
88
84
 
89
85
 
90
- def listen!(callbacks)
86
+ def listen!
91
87
  response = api("rtm.start")
92
88
  unless response["ok"]
93
89
  raise MigrationInProgress if response["error"] == "migration_in_progress"
@@ -97,7 +93,7 @@ module Slacks
97
93
 
98
94
  @websocket = Slacks::Driver.new
99
95
  websocket.connect_to websocket_url
100
- callbacks.connected
96
+ trigger "connected"
101
97
 
102
98
  websocket.on(:error) do |event|
103
99
  raise ConnectionError.new(event)
@@ -115,33 +111,34 @@ module Slacks
115
111
  @users_by_id[user["id"]] = user
116
112
  @user_id_by_name[user["name"]] = user["id"]
117
113
 
118
- when EVENT_REACTION_ADDED
119
- # Only care if someone reacted to something the bot said
120
- next unless data["item_user"] == bot.id
121
- callbacks.reaction_added(data)
122
-
123
- when EVENT_REACTION_REMOVED
124
- # Only care if someone reacted to something the bot said
125
- next unless data["item_user"] == bot.id
126
- callbacks.reaction_removed(data)
114
+ when EVENT_CHANNEL_CREATED
115
+ channel = data["channel"]
116
+ @channels_by_id[channel["id"]] = channel
117
+ @channel_id_by_name[channel["name"]] = channel["id"]
127
118
 
128
119
  when EVENT_MESSAGE
129
120
  # Don't respond to things that this bot said
130
121
  next if data["user"] == bot.id
131
122
  # ...or to messages with no text
132
123
  next if data["text"].nil? || data["text"].empty?
133
- callbacks.message(data)
134
124
  end
125
+
126
+ trigger data["type"], data
135
127
  end
136
128
 
137
129
  websocket.main_loop
138
130
 
139
131
  rescue EOFError
140
132
  # Slack hung up on us, we'll ask for a new WebSocket URL and reconnect.
141
- callbacks.error "Websocket Driver received EOF; reconnecting"
133
+ trigger "error", "Websocket Driver received EOF; reconnecting"
142
134
  retry
143
135
  end
144
136
 
137
+ EVENT_CHANNEL_CREATED = "channel_created".freeze
138
+ EVENT_GROUP_JOINED = "group_joined".freeze
139
+ EVENT_MESSAGE = "message".freeze
140
+ EVENT_USER_JOINED = "team_join".freeze
141
+
145
142
 
146
143
 
147
144
  def channels
@@ -0,0 +1,29 @@
1
+ require "concurrent/array"
2
+ require "concurrent/hash"
3
+
4
+ module Slacks
5
+ module Observer
6
+
7
+ def on(event, &block)
8
+ observers_of(event).push(block)
9
+ end
10
+
11
+ protected
12
+
13
+ def trigger(event, *args)
14
+ observers_of(event).each do |block|
15
+ block.call(*args)
16
+ end
17
+ end
18
+
19
+ def observers_of(event)
20
+ observers[event.to_sym]
21
+ end
22
+
23
+ def observers
24
+ return @observers if defined?(@observers)
25
+ @observers = Concurrent::Hash.new { |hash, key| hash[key] = Concurrent::Array.new }
26
+ end
27
+
28
+ end
29
+ end
@@ -1,3 +1,3 @@
1
1
  module Slacks
2
- VERSION = "0.2.3"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/slacks.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  require "slacks/version"
2
- require "slacks/session"
2
+ require "slacks/connection"
data/slacks.gemspec CHANGED
@@ -23,8 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.add_dependency "multi_json"
24
24
  spec.add_dependency "faraday"
25
25
  spec.add_dependency "faraday-raise-errors"
26
- spec.add_dependency "thread_safe"
27
- spec.add_dependency "attentive"
26
+ spec.add_dependency "concurrent-ruby"
28
27
 
29
28
  spec.add_development_dependency "bundler", "~> 1.10"
30
29
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slacks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Lail
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-05-30 00:00:00.000000000 Z
11
+ date: 2016-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-driver
@@ -67,21 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: thread_safe
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: attentive
70
+ name: concurrent-ruby
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
73
  - - ">="
@@ -201,13 +187,8 @@ files:
201
187
  - lib/slacks/core_ext/exception.rb
202
188
  - lib/slacks/driver.rb
203
189
  - lib/slacks/errors.rb
204
- - lib/slacks/event.rb
205
190
  - lib/slacks/guest_channel.rb
206
- - lib/slacks/listener.rb
207
- - lib/slacks/listener_collection.rb
208
- - lib/slacks/message.rb
209
- - lib/slacks/rtm_event.rb
210
- - lib/slacks/session.rb
191
+ - lib/slacks/observer.rb
211
192
  - lib/slacks/team.rb
212
193
  - lib/slacks/user.rb
213
194
  - lib/slacks/version.rb
data/lib/slacks/event.rb DELETED
@@ -1,38 +0,0 @@
1
- require "slacks/conversation"
2
-
3
- module Slacks
4
- class Event
5
- attr_reader :message, :channel, :sender
6
-
7
- def initialize(session: nil, message: nil, channel: nil, sender: nil)
8
- @session = session
9
- @message = message
10
- @channel = channel
11
- @sender = sender
12
- end
13
-
14
- def user
15
- return @user if defined?(@user)
16
- @user = sender && ::User.find_by_slack_username(sender.username)
17
- end
18
-
19
- def reply(*args)
20
- channel.reply(*args)
21
- end
22
-
23
- def random_reply(*args)
24
- channel.random_reply(*args)
25
- end
26
-
27
- def typing
28
- channel.typing
29
- end
30
-
31
- def start_conversation!
32
- Conversation.new(session, channel, sender)
33
- end
34
-
35
- private
36
- attr_reader :session
37
- end
38
- end
@@ -1,17 +0,0 @@
1
- require "attentive/listener"
2
-
3
- module Slacks
4
- class Listener < Attentive::Listener
5
- attr_accessor :conversation
6
-
7
- def matches_context?(message)
8
- contexts = message.contexts.dup
9
- contexts << :conversation if conversation && conversation.includes?(message)
10
-
11
- return false unless contexts.superset? @required_contexts
12
- return false unless contexts.disjoint? @prohibited_contexts
13
- true
14
- end
15
-
16
- end
17
- end
@@ -1,22 +0,0 @@
1
- require "attentive/listener_collection"
2
- require "slacks/listener"
3
-
4
- module Slacks
5
- class ListenerCollection < Attentive::ListenerCollection
6
-
7
- def overhear(*args, &block)
8
- options = args.last.is_a?(::Hash) ? args.pop : {}
9
- options[:context] = { in: :any }
10
- listen_for(*args, options, &block)
11
- end
12
-
13
- def listen_for(*args, &block)
14
- options = args.last.is_a?(::Hash) ? args.pop : {}
15
-
16
- Slacks::Listener.new(self, args, options, block).tap do |listener|
17
- push listener
18
- end
19
- end
20
-
21
- end
22
- end
@@ -1,51 +0,0 @@
1
- require "attentive/message"
2
-
3
- module Slacks
4
- class Message < ::Attentive::Message
5
-
6
- def initialize(session, data, params={})
7
- @session = session
8
- @data = data
9
- super data["text"], params
10
- contexts << :conversation if channel.direct_message?
11
- end
12
-
13
-
14
- def channel
15
- return @channel if defined?(@channel)
16
- @channel = session.slack.find_channel data["channel"]
17
- end
18
-
19
- def sender
20
- return @sender if defined?(@sender)
21
- @sender = session.slack.find_user data["user"]
22
- end
23
-
24
- def timestamp
25
- data["ts"]
26
- end
27
-
28
- def type
29
- data.fetch("subtype", "message")
30
- end
31
-
32
-
33
- def add_reaction(emoji)
34
- session.slack.add_reaction(emoji, self)
35
- end
36
-
37
-
38
- def respond_to_missing?(method, include_all)
39
- return true if text.respond_to?(method)
40
- super
41
- end
42
-
43
- def method_missing(method, *args, &block)
44
- return text.public_send(method, *args, &block) if text.respond_to?(method)
45
- super
46
- end
47
-
48
- private
49
- attr_reader :session, :data
50
- end
51
- end
@@ -1,30 +0,0 @@
1
- require "slacks/event"
2
-
3
- module Slacks
4
- class RtmEvent < Event
5
- attr_reader :match
6
-
7
- def initialize(session, match)
8
- @match = match
9
- @listener = match.listener
10
- message = match.message
11
- super(session: session, message: message, channel: message.channel, sender: message.sender)
12
- end
13
-
14
- def matched?(key)
15
- match.matched?(key)
16
- end
17
-
18
- def stop_listening!
19
- listener.stop_listening!
20
- end
21
-
22
- def react(emoji)
23
- message.add_reaction(emoji)
24
- end
25
-
26
- private
27
- attr_reader :listener
28
-
29
- end
30
- end
@@ -1,79 +0,0 @@
1
- require "slacks/connection"
2
- require "slacks/listener_collection"
3
- require "slacks/message"
4
- require "attentive"
5
-
6
- module Slacks
7
- class Session
8
- include Attentive
9
-
10
- attr_reader :slack
11
-
12
- def initialize(token, &block)
13
- @slack = Slacks::Connection.new(token)
14
-
15
- if block_given?
16
- listeners.instance_eval(&block)
17
- start!
18
- end
19
- end
20
-
21
- def listeners
22
- @listeners ||= Slacks::ListenerCollection.new
23
- end
24
-
25
-
26
-
27
- def start!
28
- slack.listen!(self)
29
- end
30
-
31
- def connected
32
- Attentive.invocations = [slack.bot.name, slack.bot.to_s]
33
- end
34
-
35
- def error(error_message)
36
- end
37
-
38
- def message(data)
39
-
40
- # Don't respond to things that another bot said
41
- return if data.fetch("subtype", "message") == "bot_message"
42
-
43
- # Normalize mentions of users
44
- data["text"].gsub!(/<@U[^|]+\|([^>]*)>/, %q{@\1})
45
-
46
- # Normalize mentions of channels
47
- data["text"].gsub!(/<[@#]?([UC][^>]+)>/) do |match|
48
- begin
49
- slack.find_channel($1)
50
- rescue ArgumentError
51
- match
52
- end
53
- end
54
-
55
- message = Slacks::Message.new(self, data)
56
- hear(message).each do |match|
57
-
58
- event = Slacks::RtmEvent.new(self, match)
59
- invoke! match.listener, event
60
-
61
- # Invoke only one listener per message
62
- return
63
- end
64
- end
65
-
66
- def reaction_added(data)
67
- end
68
-
69
- def reaction_removed(data)
70
- end
71
-
72
- protected
73
-
74
- def invoke!(listener, event)
75
- listener.call(event)
76
- end
77
-
78
- end
79
- end