bmf 0.2.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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.asc +11 -0
  3. data.tar.gz.asc +11 -0
  4. data/bin/bmf +8 -0
  5. data/lib/bmf.rb +70 -0
  6. data/lib/bmf/lib/address_store.rb +45 -0
  7. data/lib/bmf/lib/alert.rb +53 -0
  8. data/lib/bmf/lib/bmf.rb +364 -0
  9. data/lib/bmf/lib/folder.rb +76 -0
  10. data/lib/bmf/lib/message.rb +27 -0
  11. data/lib/bmf/lib/message_store.rb +250 -0
  12. data/lib/bmf/lib/settings.rb +74 -0
  13. data/lib/bmf/lib/thread_status.rb +98 -0
  14. data/lib/bmf/lib/xmlrpc_client.rb +27 -0
  15. data/lib/bmf/public/bitbrowser.css~ +15 -0
  16. data/lib/bmf/public/bitmessageforum.css +50 -0
  17. data/lib/bmf/public/css/bootstrap.min.css +9 -0
  18. data/lib/bmf/public/images/apple-touch-icon-114x114.png +0 -0
  19. data/lib/bmf/public/images/apple-touch-icon-144x144.png +0 -0
  20. data/lib/bmf/public/images/apple-touch-icon-57x57.png +0 -0
  21. data/lib/bmf/public/images/apple-touch-icon-72x72.png +0 -0
  22. data/lib/bmf/public/images/apple-touch-icon.png +0 -0
  23. data/lib/bmf/public/images/favicon.ico +0 -0
  24. data/lib/bmf/public/js/bootstrap.min.js +6 -0
  25. data/lib/bmf/public/js/html5.js +9 -0
  26. data/lib/bmf/public/js/jquery-1.10.1.min.js +6 -0
  27. data/lib/bmf/public/js/messagePoll.js +24 -0
  28. data/lib/bmf/views/addresses.haml +23 -0
  29. data/lib/bmf/views/compose.haml +38 -0
  30. data/lib/bmf/views/couldnt_reach_pybitmessage.haml +24 -0
  31. data/lib/bmf/views/home.haml +12 -0
  32. data/lib/bmf/views/https_quick_start.haml +71 -0
  33. data/lib/bmf/views/identities.haml +32 -0
  34. data/lib/bmf/views/layout.haml +128 -0
  35. data/lib/bmf/views/messages.haml +55 -0
  36. data/lib/bmf/views/settings.haml +18 -0
  37. data/lib/bmf/views/subscriptions.haml +40 -0
  38. data/lib/bmf/views/threads.haml +46 -0
  39. metadata +163 -0
  40. metadata.gz.asc +11 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f13d3e878a9392d5370d20f56ee5a3c27b94ea87
4
+ data.tar.gz: a67eb7df8c3c5673466d674e03e5b6a5fee54a73
5
+ SHA512:
6
+ metadata.gz: 32ff7dd7a9a1709d4b72a05f1d2fc28f6d1f231d133d4cd02e3aec073fb721036f2d92b4c0b809af764dee91c85c3fb8755a761a29bef7e67b5a0f5dec25084d
7
+ data.tar.gz: 3e892adcbe3ed2feddf347fa10dfa1a2c1b5b115a6e5c999cd58b1ff8b059cefce8f8906af90f25e7dd22fe199bf32512ef31101053854a0824c0a40ed4ec6a2
checksums.yaml.gz.asc ADDED
@@ -0,0 +1,11 @@
1
+ -----BEGIN PGP SIGNATURE-----
2
+ Version: GnuPG v1.4.12 (GNU/Linux)
3
+
4
+ iQEcBAABAgAGBQJR/lROAAoJEP5F5V2hilTWpm0H/32VyCSsOmBMr9UWs4Szcn+a
5
+ qjWp2oo0ROmVQnE2XWuPcljbMHw8jn05CsXM8b0pwkfPbJgnMNogQL/rOTGLWY1k
6
+ Qu5EDagZNJ2mxJuHjmMoOpQRZVf/ZiDImhpHKmMZbB9OmBousEB6XbR81dxDtwl/
7
+ VqltLS9RHWGW9mCbmjWsa6vqxsZ+YAEuRj2cmYcsHki0H6nhsQDoyeOgutai3p8a
8
+ C8alQdZQ4GS76145+yHp8uzaxT+VkoIamRTbwa5p5C6KEzXIvqfQ/XXnxOS2FlT4
9
+ gSm+578mG4T3dW9jNYWo5f7cX/5U2EoOoudMcyjQQO6fjjXL6QSsA0vKfohiipE=
10
+ =5m05
11
+ -----END PGP SIGNATURE-----
data.tar.gz.asc ADDED
@@ -0,0 +1,11 @@
1
+ -----BEGIN PGP SIGNATURE-----
2
+ Version: GnuPG v1.4.12 (GNU/Linux)
3
+
4
+ iQEcBAABAgAGBQJR/lRKAAoJEP5F5V2hilTWs2UH/j3lmnImIScuI9GY9cuz1Kj0
5
+ RJtRXtrwhx+7Ydmvn/gq7A24/YL7lRnWTfH8C0jT5UDk9bIxkzOJ3poxbOMnI+uf
6
+ pmpEF5xDkLQEzkjq+nr+ZMhwclZNiAZ2fdUO3urLOjq+HGQdXpRZhLazAQvYBYj3
7
+ 5RH5d1g2QbZpRtPn93QvNSf4ZuEKS4Jagc7hmf6IY3AKHUs8pD7ESwQiGuv3Yqn4
8
+ nLU3Bct0G9qYPuxa+sMgoThhNFOoXFQm6CcSPfYlIb9OYp8ajhEwpuZ8WqJFvwuG
9
+ VpXdq8SkIaIowhsiCE22rgtWjsvJ7rQhxFRARvQVDLTiUZs9pPkM6gT0HLoCb+A=
10
+ =Mk+e
11
+ -----END PGP SIGNATURE-----
data/bin/bmf ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV['RACK_ENV'] ||= "production"
4
+
5
+ require 'bundler/setup'
6
+ require 'bmf'
7
+
8
+ BMF.boot
data/lib/bmf.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'thread'
2
+
3
+ module BMF
4
+
5
+ require_relative "bmf/lib/bmf.rb"
6
+ require_relative "bmf/lib/alert.rb"
7
+
8
+ def self.puts_and_alert msg
9
+ puts msg
10
+ Alert.instance << msg
11
+ end
12
+
13
+ def self.sync
14
+ new_messages = MessageStore.instance.update
15
+ Alert.instance.add_new_messages(new_messages)
16
+ AddressStore.instance.update
17
+
18
+ rescue Errno::ECONNREFUSED => ex
19
+ puts_and_alert "Background sync. Couldn't connect to PyBitmessage server. Is it running with the API enabled? "
20
+ rescue JSON::ParserError => ex
21
+ puts_and_alert "Couldn't background sync. It seems like PyBitmessage is running but refused access. Do you have the correct info in config/settings.yml? "
22
+ rescue Exception => ex
23
+ puts_and_alert "Background Sync failed with #{ex.message}"
24
+ end
25
+
26
+ def self.boot
27
+ puts "Doing initial sync before starting..."
28
+ sync
29
+ Alert.instance.pop_new_messages # don't show new message alert on bood.
30
+
31
+ Thread.new do
32
+
33
+ while(1) do
34
+ sync_interval = Settings.instance.sync_interval.to_i
35
+ sync_interval = 60 if sync_interval == 0
36
+ sleep(sync_interval)
37
+ begin
38
+ sync
39
+ rescue Exception => ex
40
+ msg = "Background sync faild with #{ex.message}"
41
+ Alert.instance << msg
42
+ puts msg
43
+ puts ex.backtrace.join("\n")
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ BMF.run! do |server|
50
+ settings = Settings.instance
51
+
52
+ if (settings.https_server_key_file && settings.https_server_key_file != "") &&
53
+ (settings.https_server_certificate_file && settings.https_server_certificate_file != "")
54
+
55
+ puts "Requiring https..."
56
+ ssl_options = {
57
+ :cert_chain_file => settings.https_server_certificate_file,
58
+ :private_key_file => settings.https_server_key_file,
59
+ :verify_peer => false
60
+ }
61
+ server.ssl = true
62
+ server.ssl_options = ssl_options
63
+ else
64
+ puts "NOT requiring https. Traffic is unencrypted..."
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,45 @@
1
+ require 'singleton'
2
+ require 'base64'
3
+ require 'json'
4
+ require 'thread'
5
+
6
+ require_relative 'xmlrpc_client.rb'
7
+
8
+ class BMF::AddressStore
9
+ include Singleton
10
+
11
+ # attr_reader :addresses
12
+
13
+ def addresses
14
+ Mutex.new.synchronize { @addresses.dup.freeze }
15
+ end
16
+
17
+ def initialize
18
+ @addresses = {}
19
+ # update
20
+ end
21
+
22
+ def log x
23
+ puts x
24
+ end
25
+
26
+ def update
27
+ address_infos = JSON.parse(BMF::XmlrpcClient.instance.listAddresses)['addresses']
28
+
29
+ lock = Mutex.new
30
+
31
+ lock.synchronize do
32
+ new_addresses = 0
33
+
34
+ address_infos.each do |address_info|
35
+ address = address_info['address']
36
+ if !@addresses.has_key? address
37
+ new_addresses += 1
38
+ @addresses[address] = address_info
39
+ end
40
+ end
41
+
42
+ log "Added #{new_addresses} addresses..." if new_addresses > 0
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,53 @@
1
+ require 'singleton'
2
+ require 'thread'
3
+
4
+ class BMF::Alert
5
+ include Singleton
6
+
7
+ def initialize
8
+ @alerts = []
9
+ @new_messages = 0
10
+ end
11
+
12
+ def << alert
13
+ Mutex.new.synchronize do
14
+ if !@alerts.include? alert
15
+ @alerts << alert
16
+ end
17
+ end
18
+ end
19
+
20
+ def peek
21
+ Mutex.new.synchronize do
22
+ @alerts.dup.freeze
23
+ end
24
+ end
25
+
26
+ def pop
27
+ Mutex.new.synchronize do
28
+ alerts = @alerts.dup.freeze
29
+ @alerts = []
30
+ alerts
31
+ end
32
+ end
33
+
34
+ def add_new_messages i
35
+ Mutex.new.synchronize do
36
+ @new_messages += i
37
+ end
38
+ end
39
+
40
+ def peek_new_messages
41
+ Mutex.new.synchronize do
42
+ @new_messages
43
+ end
44
+ end
45
+
46
+ def pop_new_messages
47
+ Mutex.new.synchronize do
48
+ i = @new_messages
49
+ @new_messages = 0
50
+ i
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,364 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/reloader'
3
+ require 'sinatra/cookies'
4
+
5
+ require 'haml'
6
+ require 'rdiscount'
7
+
8
+ require_relative 'message_store.rb'
9
+ require_relative 'address_store.rb'
10
+ require_relative 'thread_status.rb'
11
+ require_relative 'message.rb'
12
+ require_relative 'settings.rb'
13
+ require_relative 'folder.rb'
14
+
15
+ require 'sanitize'
16
+
17
+ class BMF::BMF < Sinatra::Base
18
+ helpers Sinatra::Cookies
19
+
20
+ helpers do
21
+
22
+ def safe_html html
23
+ local_images_only = Sanitize::Config::RELAXED.dup
24
+ local_images_only[:protocols]["img"]["src"] = ["data"]
25
+
26
+ Sanitize.clean(html.force_encoding("UTF-8"), local_images_only)
27
+ end
28
+
29
+ # If we've got some html, make it safe
30
+ def safe_text text
31
+ markdown_content_type = "# Content-Type: text/markdown"
32
+ starts_with_markdown = text.strip.start_with? markdown_content_type
33
+ if !text.include?("<") && !starts_with_markdown
34
+ return "<blockquote>" + text.gsub("\n","<br />\n") + "</blockquote>"
35
+ end
36
+
37
+ if BMF::Settings.instance.display_sanitized_html != 'yes'
38
+ return "<blockquote>" + CGI::escape_html(text).gsub("\n", "<br />\n") + "</blockqoute>"
39
+ end
40
+
41
+ if text.strip.start_with? markdown_content_type
42
+ text = RDiscount.new(text.sub(markdown_content_type, "")).to_html
43
+ end
44
+
45
+ safe_html(text)
46
+
47
+ end
48
+
49
+ end
50
+
51
+
52
+ set :server, 'thin'
53
+ set :root, File.expand_path("../../", __FILE__)
54
+ set :layout, :layout
55
+
56
+ settings = BMF::Settings.instance
57
+ set :bind, settings.server_interface if settings.server_interface
58
+ set :port, settings.server_port if settings.server_port
59
+
60
+ configure :development do
61
+ register Sinatra::Reloader
62
+ end
63
+
64
+ if (settings.user && settings.user != "")
65
+ if (settings.password && settings.password != "")
66
+ puts "Requiring http basic authentication..."
67
+ use Rack::Auth::Basic, "Current BMF settings require authentication" do |username, password|
68
+ username == settings.user and password == settings.password
69
+ end
70
+ else
71
+ puts "You specified a username but no password! Refusing to use http basic authentication..."
72
+ end
73
+ end
74
+
75
+ get "/", :provides => :html do
76
+ haml :home
77
+ end
78
+
79
+ def load_settings
80
+ @settings = {}
81
+
82
+ BMF::Settings::VALID_SETTINGS.each do |key|
83
+ @settings[key] = BMF::Settings.instance.send(key)
84
+ end
85
+ end
86
+
87
+ get "/configuring-pybitmessage/", :provides => :html do
88
+ haml :couldnt_reach_pybitmessage
89
+ end
90
+
91
+ get "/https-quick-start/", :provides => :html do
92
+ haml :https_quick_start
93
+ end
94
+
95
+ get "/json/new_messages/", :provides => :json do
96
+ {:new_messages => BMF::Alert.instance.peek_new_messages}.to_json
97
+ end
98
+
99
+ get "/identities/", :provides => :html do
100
+ BMF::AddressStore.instance.update
101
+ @addresses = BMF::AddressStore.instance.addresses
102
+
103
+ haml :identities
104
+ end
105
+
106
+ post "/identities/new/", :provides => :html do
107
+ if params[:label]
108
+ label = Base64.encode64(params[:label])
109
+ response = BMF::XmlrpcClient.instance.createRandomAddress label
110
+ if BMF::XmlrpcClient.is_error? response
111
+ halt(500, "Couldn't create address: #{response}")
112
+ else
113
+ haml("Created random address #{response} with label #{params[:label]}")
114
+ end
115
+ elsif params[:passphrase]
116
+ passphrase = Base64.encode64(params[:passphrase])
117
+ response = BMF::XmlrpcClient.instance.createDeterministicAddresses passphrase
118
+ if BMF::XmlrpcClient.is_error? response
119
+ halt(500, "Couldn't create address: #{response}")
120
+ else
121
+ addresses = JSON.parse(response)['addresses']
122
+ if addresses.empty?
123
+ haml("Address already exists")
124
+ else
125
+ haml("Created address #{addresses.join(', ')}")
126
+ end
127
+ end
128
+ else
129
+ raise "Bad submission"
130
+ end
131
+
132
+ end
133
+
134
+ get "/settings/", :provides => :html do
135
+ load_settings
136
+ haml :settings
137
+ end
138
+
139
+ post "/settings/update", :provides => :html do
140
+ params.each_pair do |key, value|
141
+ BMF::Settings.instance.update(key,value)
142
+ end
143
+ BMF::Settings.instance.persist
144
+
145
+ @flash = "BMF::Settings updated!"
146
+
147
+ load_settings
148
+ haml :settings
149
+ end
150
+
151
+ get "/messages/compose/", :provides => :html do
152
+ @to = params[:to]
153
+ @from = params[:from]
154
+ @subject = params[:subject]
155
+
156
+ if (@from.nil? || @from == "") && BMF::Settings.instance.default_send_address
157
+ @from = BMF::Settings.instance.default_send_address
158
+ end
159
+
160
+ if params[:reply_to]
161
+ @message = "&nbsp\n------------------------------------------------------\n" + BMF::MessageStore.instance.messages[params[:reply_to]]['message']
162
+ else
163
+ @message = params[:message]
164
+ end
165
+
166
+ @message = "" if @message.nil?
167
+ if BMF::Settings.instance.sig
168
+ @message = "&nbsp;\n" + BMF::Settings.instance.sig + "\n" + @message
169
+ end
170
+
171
+ haml :compose
172
+ end
173
+
174
+ post "/messages/send/", :provides => :html do
175
+ to = params[:to]
176
+ from = params[:from]
177
+ subject = Base64.encode64(params[:subject])
178
+ message = Base64.encode64(params[:message])
179
+ broadcast = params[:broadcast]
180
+
181
+
182
+ res = "Sending message in background..."
183
+
184
+ Thread.new do
185
+ begin
186
+ if broadcast
187
+ res = BMF::XmlrpcClient.instance.sendBroadcast(from, subject, message)
188
+ else
189
+ res = BMF::XmlrpcClient.instance.sendMessage(to, from, subject, message)
190
+ end
191
+
192
+ if BMF::XmlrpcClient.is_error? res
193
+ BMF::Alert.instance << "BACKGROUND SEND FAILED! #{res}"
194
+ else
195
+ BMF::Alert.instance << "Background send seemed to finish successfully"
196
+ end
197
+ rescue Exception => ex
198
+ BMF::Alert.instance << "BACKGROUND SEND FAILED! #{ex.message}"
199
+ end
200
+ end
201
+
202
+ confirm_message = "Sending in background..."
203
+ if params[:goto] && params[:goto] != ""
204
+ BMF::Alert.instance << confirm_message
205
+ redirect params[:goto]
206
+ else
207
+ haml confirm_message
208
+ end
209
+
210
+ end
211
+
212
+ post "/messages/delete/", :provides => :html do
213
+ msgid = params[:msgid]
214
+
215
+ res = BMF::XmlrpcClient.instance.trashSentMessage(msgid)
216
+ if BMF::XmlrpcClient.is_error? res
217
+ halt(500, haml("Delete failed. #{res}"))
218
+ end
219
+
220
+ res = BMF::XmlrpcClient.instance.trashMessage(msgid)
221
+ if BMF::XmlrpcClient.is_error? res
222
+ halt(500, haml("Delete failed. #{res}"))
223
+ end
224
+
225
+ if request.referrer and request.referrer != ""
226
+ cookies[:flash] = res
227
+ redirect request.referrer
228
+ else
229
+ haml("#{res} [#{msgid}]")
230
+ end
231
+
232
+ end
233
+
234
+ get "/subscriptions/", :provides => :html do
235
+
236
+ res = BMF::XmlrpcClient.instance.listSubscriptions
237
+
238
+ if BMF::XmlrpcClient.is_error? res
239
+ @subscriptions = []
240
+ else
241
+ @subscriptions = JSON.parse(res)['subscriptions']
242
+ end
243
+
244
+ haml :subscriptions
245
+ end
246
+
247
+ post "/subscriptions/create/", :provides => :html do
248
+ res = BMF::XmlrpcClient.instance.addSubscription params[:address], Base64.encode64(params[:label])
249
+
250
+ if BMF::XmlrpcClient.is_error? res
251
+ halt(500, haml("Error subscribing. #{res}"))
252
+ else
253
+ haml("Subscribed. #{res}")
254
+ end
255
+ end
256
+
257
+ post "/subscriptions/delete/", :provides => :html do
258
+ res = BMF::XmlrpcClient.instance.deleteSubscription params[:address]
259
+ if BMF::XmlrpcClient.is_error? res
260
+ halt(500, haml("Error deleting subscrition. #{res}"))
261
+ else
262
+ haml("Subscription Deleted. #{res}")
263
+ end
264
+ end
265
+
266
+
267
+ get "/:folder/", :provides => :html do
268
+ folder = BMF::Folder.new(params[:folder])
269
+ @messages = folder.messages(:sort => :new)
270
+ @addresses = BMF::AddressStore.instance.addresses
271
+ haml :addresses
272
+ end
273
+
274
+ get "/:folder/:address/", :provides => :html do
275
+ @addresses = BMF::AddressStore.instance.addresses
276
+ folder = BMF::Folder.new(params[:folder])
277
+ @address = params[:address]
278
+ @threads = folder.threads_for_address(@address, :sort => :new)
279
+
280
+ halt(404, haml("Couldn't find any threads matching #{params[:address]}. Maybe you trashed them all.")) if @threads.nil?
281
+
282
+ haml :threads
283
+ end
284
+
285
+ get "/:folder/:address/:thread", :provides => :html do
286
+ @folder = BMF::Folder.new(params[:folder])
287
+
288
+ @address = params[:address]
289
+
290
+ @thread = CGI.unescape(params[:thread])
291
+ @messages = @folder.thread_messages(@address, @thread, :sort => :old)
292
+
293
+ halt(404, haml("Couldn't find any messages for thread #{params[:thread].inspect} for address #{params[:address].inspect}. Maybe you trashed the messages.")) if @messages.nil?
294
+
295
+ @addresses = BMF::AddressStore.instance.addresses
296
+
297
+ # Get the last time we visited thread, and update to now
298
+ @thread_last_visited = BMF::ThreadStatus.instance.thread_last_visited(@address,@thread)
299
+ BMF::ThreadStatus.instance.thread_visited(@address, @thread, BMF::Message.time(@messages.last)) if @messages.last
300
+
301
+ haml :messages
302
+ end
303
+
304
+ post "/:folder/thread/delete", :provides => :html do
305
+ folder = BMF::Folder.new params[:folder]
306
+ address = params[:address]
307
+ thread = params[:thread]
308
+
309
+ delete_statuses = folder.delete_thread(address, thread)
310
+
311
+ if !delete_statuses.empty?
312
+ delete_status_lines = delete_statuses.map { |x| "<li>#{x}</li>"}.join
313
+ haml ("Deleted:<ol>#{delete_status_lines}</ol>")
314
+ else
315
+ halt(500, haml("No messages found for this thread!"))
316
+ end
317
+
318
+ end
319
+
320
+ post "/:folder/thread/bulk_modify", :provides => :html do
321
+ folder = BMF::Folder.new(params[:folder])
322
+ address = params[:address]
323
+
324
+ update_action = params[:update_action]
325
+ threads_to_update = params.select { |key, value| key =~ /^thread__/}.values
326
+
327
+ updates_list = threads_to_update.map{|t| "<li>#{CGI.escape_html(t)}</li>"}.join
328
+ updates_list = "<ul>" + updates_list + "</ul>"
329
+
330
+ case update_action
331
+ when "noop"
332
+ status = "Noop. Did nothing to:"
333
+ when "delete"
334
+ address = params[:address]
335
+ threads_to_update.each do |thread|
336
+ folder.delete_thread(address, thread)
337
+ end
338
+
339
+ status = "Deleted the following threads:"
340
+ when "mark_read"
341
+ threads_to_update.each do |thread|
342
+ BMF::ThreadStatus.instance.thread_visited(address, thread, Time.now.to_i)
343
+ end
344
+
345
+ status = "Marked the following threads as read."
346
+ when "mark_all_read"
347
+
348
+ folder.threads_for_address(address).each do |thread, thread_info|
349
+ BMF::ThreadStatus.instance.thread_visited(address, thread, Time.now.to_i)
350
+ end
351
+
352
+ status = "Marked all threads as read."
353
+ else
354
+ raise "Unknown Action #{update_action}"
355
+ end
356
+
357
+ cookies[:flash] = "<div>#{status}#{updates_list}</div>"
358
+ redirect "/#{folder.name}/#{address}/"
359
+ end
360
+
361
+ run! if app_file == $0
362
+
363
+ end
364
+