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.
- checksums.yaml +15 -0
- data/Gemfile +31 -0
- data/Gemfile.lock +93 -0
- data/README.md +130 -0
- data/Rakefile +41 -9
- data/bin/fbomb +226 -31
- data/fbomb.gemspec +51 -8
- data/images/planet/1dEzGbz.jpg +0 -0
- data/images/planet/3aASzbv.jpg +0 -0
- data/images/planet/BCfEwgl.jpg +0 -0
- data/images/planet/EPbgqjy.jpg +0 -0
- data/images/planet/FoiDgwc.jpg +0 -0
- data/images/planet/HJiUPV2.jpg +0 -0
- data/images/planet/KOcvGjw.jpg +0 -0
- data/images/planet/L5n5lPK.jpg +0 -0
- data/images/planet/MaGKipv.jpg +0 -0
- data/images/planet/QVRALtz.jpg +0 -0
- data/images/planet/W3fWkTZ.jpg +0 -0
- data/images/planet/about.md +1 -0
- data/images/planet/nWqdkF7.jpg +0 -0
- data/images/planet/w41sv1T.jpg +0 -0
- data/images/planet/xySa4GD.jpg +0 -0
- data/images/planet/yzw8pOQ.jpg +0 -0
- data/lib/fbomb.rb +99 -19
- data/lib/fbomb/campfire.rb +39 -11
- data/lib/fbomb/command.rb +52 -15
- data/lib/fbomb/commands/builtin.rb +720 -9
- data/lib/fbomb/commands/system.rb +52 -10
- data/lib/fbomb/flowdock.rb +307 -0
- data/lib/fbomb/util.rb +65 -0
- data/lib/fbomb/uuid.rb +312 -0
- data/notes/ara.txt +54 -0
- metadata +229 -113
- data/README +0 -20
@@ -1,17 +1,59 @@
|
|
1
1
|
FBomb {
|
2
2
|
command(:help) {
|
3
3
|
call do |*args|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
data/lib/fbomb/util.rb
CHANGED
@@ -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
|