bmf 0.2.0

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