fbomb 1.0.0 → 2.0.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.
@@ -1,17 +1,59 @@
1
1
  FBomb {
2
2
  command(:help) {
3
3
  call do |*args|
4
- sections = []
5
- Command.table.each do |path, command|
6
- next if path == '/help'
7
- help = command.help || path
8
- chunk = [path, Util.indent(help) + "\n"]
9
- sections.push(chunk)
4
+ if args.join(' ') =~ /me/i
5
+ speak("You are beyond help...")
6
+ else
7
+ sections = []
8
+
9
+ Command.table.each do |key, command|
10
+ next if key == '.help'
11
+ help = command.help || key
12
+ chunk = [key, Util.indent(help) + "\n"]
13
+ sections.push(chunk)
14
+ end
15
+
16
+ sections.sort!{|a, b| a.first <=> b.first}
17
+
18
+ sections.push([".help", Util.indent("this message") + "\n"])
19
+
20
+ msg = sections.join("\n")
21
+
22
+ paste(msg, :tag => :help) unless msg.strip.empty?
23
+ end
24
+ end
25
+ }
26
+
27
+ command(:reload){
28
+ help 'reload fbomb commands'
29
+
30
+ call do |*args|
31
+ #Thread.critical = true
32
+ table = FBomb::Command.table
33
+
34
+ begin
35
+ FBomb::Command.table = FBomb::Command::Table.new
36
+ FBomb::Command.load(Command.command_paths)
37
+
38
+ messages = [
39
+ "locked and loaded.",
40
+ "locked, cocked, and ready to rock.",
41
+ "let's roll.",
42
+ "it's time to kick ass and chew bubble gum. and i'm all out of gum.",
43
+ "let's do this.",
44
+ "ERROR! no just kidding, it's all good",
45
+ "we need guns. lots of guns.",
46
+ "beep boop reloaded",
47
+ "awww yeah, let's rock!"
48
+ ]
49
+ speak(messages.sort_by { rand }.first)
50
+ rescue Object => e
51
+ #msg = "#{ e.message }(#{ e.class })\n#{ Array(e.backtrace).join(10.chr) }"
52
+ #speak(msg)
53
+ FBomb::Command.table = table
54
+ ensure
55
+ #Thread.critical = false
10
56
  end
11
- sections.sort!{|a, b| a.first <=> b.first}
12
- sections.push(["/help", Util.indent("this message") + "\n"])
13
- msg = sections.join("\n")
14
- paste(msg) unless msg.strip.empty?
15
57
  end
16
58
  }
17
59
 
@@ -0,0 +1,307 @@
1
+ module FBomb
2
+ class Flowdock
3
+ fattr(:organization)
4
+ fattr(:flow)
5
+ fattr(:token)
6
+ fattr(:options)
7
+ fattr(:client)
8
+ fattr(:client_options)
9
+
10
+ def initialize(organization, flow, token, options = {})
11
+ raise ArgumentError.new('no organization') if organization.to_s.strip.empty?
12
+ raise ArgumentError.new('no flow') if flow.to_s.strip.empty?
13
+ raise ArgumentError.new('no token') if token.to_s.strip.empty?
14
+
15
+ @organization = Organization.new(organization, self)
16
+ @flow = Flow.new(flow, self)
17
+ @token = String.new(token)
18
+
19
+ @options = Map.for(options)
20
+ @client_options = Map.for(@options.get(:client))
21
+
22
+ unless @client_options.has_key?(:token)
23
+ unless @token.empty?
24
+ @client_options[:api_token] ||= @token
25
+ end
26
+ end
27
+
28
+ @client = ::Flowdock::Flow.new(@client_options)
29
+ end
30
+
31
+ class Object
32
+ fattr(:name)
33
+ fattr(:flowdock)
34
+
35
+ def initialize(name, flowdock)
36
+ @name = name.to_s.strip.downcase
37
+ @flowdock = flowdock
38
+ end
39
+
40
+ def to_s
41
+ name
42
+ end
43
+
44
+ def to_str
45
+ name
46
+ end
47
+
48
+ %w( organization flow token client options ).each do |method|
49
+ class_eval <<-__
50
+ def #{ method }(*args, &block)
51
+ flowdock.#{ method }(*args, &block) if flowdock
52
+ end
53
+ __
54
+ end
55
+ end
56
+
57
+ class Organization < Object
58
+ end
59
+
60
+ class Flow < Object
61
+ def escape(string)
62
+ string.to_s.gsub('+', '%2B')
63
+ end
64
+
65
+ def tags_for(*tags)
66
+ Coerce.list_of_strings(*tags).map{|tag| "##{ tag }".gsub(/^[#]+/, '#')}
67
+ end
68
+
69
+ def speak(*args, &block)
70
+ options = Map.options_for!(args)
71
+ tags = tags_for(options[:tags], options[:tag])
72
+
73
+ content = escape(Coerce.list_of_strings(args).join(' '))
74
+
75
+ msg = {:content => content, :tags => tags}
76
+
77
+ if FBomb.debug
78
+ puts("SPEAK\n")
79
+ puts(msg.to_yaml)
80
+ puts
81
+ else
82
+ client.push_to_chat(msg)
83
+ end
84
+ end
85
+
86
+ alias_method(:say, :speak)
87
+
88
+ def paste(*args, &block)
89
+ options = Map.options_for!(args)
90
+ tags = tags_for(options[:tags], options[:tag])
91
+
92
+ content =
93
+ case
94
+ when args.size == 1
95
+ if args.first.is_a?(String)
96
+ args.first
97
+ else
98
+ Coerce.array(args.first).join("\n")
99
+ end
100
+ else
101
+ Coerce.list_of_strings(args).join("\n")
102
+ end
103
+
104
+ msg = {:content => Util.indent(escape(content), 4), :tags => tags}
105
+
106
+ if FBomb.debug
107
+ puts("PASTE\n")
108
+ puts(msg.to_yaml)
109
+ puts
110
+ else
111
+ client.push_to_chat(msg)
112
+ end
113
+ end
114
+
115
+ def upload(*args, &block)
116
+ raise NotImplementedError
117
+ end
118
+
119
+ def users(*args, &block)
120
+ []
121
+ end
122
+
123
+ def leave
124
+ say 'bai'
125
+ end
126
+
127
+ =begin
128
+ {"event"=>"activity.user",
129
+ "tags"=>[],
130
+ "uuid"=>nil,
131
+ "persist"=>false,
132
+ "id"=>194342,
133
+ "flow"=>"c6dbc029-2173-4fb6-a423-32293c373106",
134
+ "content"=>{"last_activity"=>1399656686378},
135
+ "sent"=>1399657205286,
136
+ "app"=>nil,
137
+ "attachments"=>[],
138
+ "user"=>"76002"}
139
+
140
+ {:flow=>{"event"=>"message",
141
+ "tags"=>[],
142
+ "uuid"=>"Ry2GHegX445OAxV_",
143
+ "id"=>2427,
144
+ "flow"=>"affcb403-0c5f-4a2c-89a6-a809a887e281",
145
+ "content"=>".peeps",
146
+ "sent"=>1399838702954,
147
+ "app"=>"chat",
148
+ "attachments"=>[],
149
+ "user"=>"77414"}
150
+ }
151
+
152
+ =end
153
+ def stream(&block)
154
+ return debug_stream(&block) if FBomb.debug
155
+
156
+ http = EM::HttpRequest.new(
157
+ "https://stream.flowdock.com/flows/#{ organization }/#{ flow }",
158
+ :keepalive => true, :connect_timeout => 0, :inactivity_timeout => 0)
159
+
160
+ EventMachine.run do
161
+ s = http.get(:head => { 'Authorization' => [token, ''], 'accept' => 'application/json'})
162
+
163
+ buffer = ""
164
+ s.stream do |chunk|
165
+ buffer << chunk
166
+ while line = buffer.slice!(/.+\r\n/)
167
+ begin
168
+ flow = JSON.parse(line)
169
+
170
+ unless flow['event'] == 'activity.user'
171
+ history.push(flow)
172
+
173
+ while history.size > 1024
174
+ history.shift
175
+ end
176
+ end
177
+
178
+ if flow['external_user_name'] == client.external_user_name
179
+ next
180
+ end
181
+
182
+ block.call(Map.for(flow)) if block
183
+ rescue Object => e
184
+ warn("#{ e.message }(#{ e.class })#{ Array(e.backtrace).join(10.chr) }")
185
+ # FIXME
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ def debug_stream(&block)
193
+ require "readline"
194
+
195
+ while buf = Readline.readline("#{ organization }/#{ flow } > ", true)
196
+ if buf.strip.downcase == 'exit'
197
+ exit
198
+ end
199
+
200
+ flow = {
201
+ :event => 'message',
202
+ :content => buf,
203
+ :tags => [],
204
+ :persist => false,
205
+ :id => rand(99999),
206
+ :uuid => FBomb.uuid,
207
+ :sent => Time.now.to_i,
208
+ :app => 'fbomb',
209
+ :attachments => [],
210
+ :user => rand(99999),
211
+ }
212
+
213
+ block.call(Map.for(flow)) if block
214
+ end
215
+ end
216
+
217
+ def history
218
+ @history ||= []
219
+ end
220
+
221
+ def users
222
+ url = "https://#{ token }:@api.flowdock.com/flows/#{ organization }/#{ flow }/users"
223
+ key = url
224
+
225
+ cache.fetch(key) do
226
+ json = `curl -s #{ url.inspect }` # FIXME - boo for shelling out ;-/
227
+ array = JSON.parse(json)
228
+ users = array.map{|u| User.for(u)}
229
+ end
230
+ end
231
+
232
+ def user_for(id)
233
+ users.detect{|user| user.id.to_s == id.to_s || user.email.to_s == id.to_s}
234
+ end
235
+
236
+ def cache
237
+ Cache
238
+ end
239
+ end
240
+
241
+ module Cache
242
+ TTL = 3600
243
+
244
+ def read(key)
245
+ entry = _cache[key]
246
+
247
+ return nil unless entry
248
+
249
+ value, cached_at = entry
250
+ expired = (Time.now - cached_at) > TTL
251
+
252
+ if expired
253
+ _cache.delete(key)
254
+ nil
255
+ else
256
+ value
257
+ end
258
+ end
259
+
260
+ def write(key, value)
261
+ cached_at = Time.now
262
+ entry = [value, cached_at]
263
+ _cache[key] = entry
264
+ value
265
+ end
266
+
267
+ def _cache
268
+ @_cache ||= Map.new
269
+ end
270
+
271
+ def fetch(key, &block)
272
+ entry = _cache[key]
273
+
274
+ if entry
275
+ value, cached_at = entry
276
+ expired = (Time.now - cached_at) > TTL
277
+
278
+ if expired
279
+ begin
280
+ write(key, block.call)
281
+ rescue Object => e
282
+ warn "#{ e.message }(#{ e.class })"
283
+ value
284
+ end
285
+ else
286
+ value
287
+ end
288
+ else
289
+ write(key, block.call)
290
+ end
291
+ end
292
+
293
+ extend(Cache)
294
+ end
295
+
296
+ =begin
297
+ users = JSON.parse(`curl -s
298
+ https://829ac2ae34fd4c8998cf6220d43dd3de:@api.flowdock.com/flows/dojo4/dojo4/users`).map{|u|
299
+ Map.for(u)}
300
+ =end
301
+ class User < ::Map
302
+ end
303
+
304
+ class Token < Object
305
+ end
306
+ end
307
+ end
@@ -1,4 +1,53 @@
1
1
  module Util
2
+ def hostname
3
+ @hostname ||= (Socket.gethostname rescue 'localhost')
4
+ end
5
+
6
+ def pid
7
+ @pid ||= Process.pid
8
+ end
9
+
10
+ def ppid
11
+ @ppid ||= Process.ppid
12
+ end
13
+
14
+ def thread_id
15
+ Thread.current.object_id.abs
16
+ end
17
+
18
+ def tmpdir(*args, &block)
19
+ options = Hash === args.last ? args.pop : {}
20
+
21
+ dirname = Dir.tmpdir
22
+
23
+ return dirname unless block
24
+
25
+ turd = options['turd'] || options[:turd]
26
+
27
+ basename = [ hostname, ppid, pid, thread_id, rand ].join('-')
28
+
29
+ made = false
30
+
31
+ 42.times do |n|
32
+ pathname = File.join(dirname, "#{ basename }-n=#{ n }")
33
+
34
+ begin
35
+ FileUtils.mkdir_p(pathname)
36
+ made = true
37
+ return Dir.chdir(pathname, &block)
38
+ rescue Object
39
+ sleep(rand)
40
+ :retry
41
+ ensure
42
+ unless turd
43
+ FileUtils.rm_rf(pathname) if made
44
+ end
45
+ end
46
+ end
47
+
48
+ raise "failed to make tmpdir in #{ dirname.inspect }"
49
+ end
50
+
2
51
  def paths_for(*args)
3
52
  path = args.flatten.compact.join('/')
4
53
  path.gsub!(%r|[.]+/|, '/')
@@ -28,6 +77,22 @@ module Util
28
77
  absolute_path_for(arg, *args)
29
78
  end
30
79
 
80
+ %w(
81
+ paths_for
82
+ absolute_path_for
83
+ absolute_prefix_for
84
+ path_for
85
+ normalize_path
86
+ ).each do |src, dst|
87
+ dst = src.gsub('path', 'key')
88
+
89
+ module_eval <<-__
90
+ def #{ dst }(*args, &block)
91
+ #{ src }(*args, &block).gsub('/', '.')
92
+ end
93
+ __
94
+ end
95
+
31
96
  def indent(chunk, n = 2)
32
97
  lines = chunk.split %r/\n/
33
98
  re = nil