fbomb 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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