thimbl 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest CHANGED
@@ -9,10 +9,13 @@ lib/thimbl/base.rb
9
9
  lib/thimbl/command.rb
10
10
  lib/thimbl/finger.rb
11
11
  lib/thimbl/utils.rb
12
- test/fixtures/cache.json
13
- test/fixtures/cache_error_nil.json
14
12
  test/fixtures/finger_dk_telekommunisten_org.txt
15
13
  test/fixtures/finger_dk_telekommunisten_org_two_break_lines.txt
14
+ test/fixtures/messages_1.json
15
+ test/fixtures/messages_2.json
16
+ test/fixtures/messages_3.json
17
+ test/fixtures/official_plan.json
18
+ test/fixtures/print.txt
16
19
  test/test_helper.rb
17
20
  test/thimbl_base_test.rb
18
21
  test/thimbl_command_test.rb
data/README.md CHANGED
@@ -6,15 +6,20 @@ I have follow the style of the [Thimbl Python client](https://github.com/blippy/
6
6
 
7
7
  ## Commands
8
8
 
9
- * follow
10
9
  * fetch
10
+ * follow
11
11
  * post
12
- * print
13
12
  * push
14
13
 
14
+ ## Attributes
15
+
16
+ * messages
17
+ * following
18
+ * properties
19
+
15
20
  ## Version
16
21
 
17
- This version is in development, not ready for any production environment.
22
+ This version is in development, use it in production environment under your own responsability.
18
23
 
19
24
  ## Install
20
25
 
@@ -24,18 +29,20 @@ This version is in development, not ready for any production environment.
24
29
 
25
30
  require 'rubygems'
26
31
  require 'thimbl'
27
- thimbl =
32
+ thimbl =
28
33
  Thimbl::Base.new(
29
- 'bio' => 'my bio',
30
- 'website' => 'my website',
31
- 'mobile' => 'my mobile',
32
- 'email' => 'my email',
33
- 'address' => 'me@thimbl.net',
34
- 'name' => 'my name'
34
+ 'user@thimbl.net',
35
+ {
36
+ :bio => 'my bio',
37
+ :website => 'my website',
38
+ :mobile => 'my mobile',
39
+ :email => 'my email',
40
+ :name => 'my name'
41
+ }
35
42
  )
36
43
  thimbl.follow 'dk', 'dk@telekommunisten.org'
37
44
  thimbl.fetch
38
- thimbl.print
45
+ thimbl.messages
39
46
  thimbl.post 'My first post'
40
47
  thimbl.push 'password'
41
48
 
@@ -44,15 +51,11 @@ This version is in development, not ready for any production environment.
44
51
  The gem comes with a *shell command*, you can use it like this:
45
52
 
46
53
  thimblr setup 'user@thimblrserver.com'
47
- thimblr follow 'dk' 'dk@telekommunisten.org'
48
- thimblr fetch
54
+ thimblr follow 'dk' 'dk@telekommunisten.org' 'my password'
49
55
  thimblr print
50
- thimblr post "My first message :)"
51
- thimblr push <password>
56
+ thimblr post 'My first message :)' 'my password'
52
57
 
53
58
  ## TODO
54
59
 
55
60
  * Support *simbolize* hash keys
56
- * In the Thimbl::Command.setup ask for the rest of the configuration options *bio*, *mobile*, ...
57
- * thimbl.unfollow
58
- * ERROR: If finger respond empty Plan
61
+ * Reply to another message support
data/Rakefile CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'echoe'
4
4
 
5
- Echoe.new('thimbl', '0.1.2') do |p|
5
+ Echoe.new('thimbl', '0.2.0') do |p|
6
6
  p.description = "Small client for the distributed microbloging protocol: [thimbl](http://www.thimbl.net/)"
7
7
  p.url = "http://github.com/fguillen/ThimblClient"
8
8
  p.author = "Fernando Guillen"
@@ -3,10 +3,8 @@
3
3
  # Use:
4
4
  # thimblr setup 'user@thimblrserver.com'
5
5
  # thimblr follow 'dk' 'dk@telekommunisten.org'
6
- # thimblr fetch
7
6
  # thimblr print
8
7
  # thimblr post "My first message :)"
9
- # thimblr push
10
8
 
11
9
  begin
12
10
  require 'thimbl'
@@ -16,18 +14,17 @@ rescue LoadError
16
14
  end
17
15
 
18
16
  if ARGV.empty?
19
- puts "use: $ thimbl <command>"
17
+ puts "use: $ thimblr <command>"
20
18
  exit 1
21
19
  end
22
20
 
23
21
  begin
24
22
  case ARGV[0]
25
23
  when 'setup'; Thimbl::Command.setup( *ARGV[1..4] ); puts "setup completed"
24
+ when 'version'; puts Thimbl::Command.version
26
25
  when 'print'; puts Thimbl::Command.print
27
- when 'fetch'; Thimbl::Command.fetch; puts "fecth completed"
28
- when 'post'; Thimbl::Command.post ARGV[1]; puts "post completed"
29
- when 'push'; Thimbl::Command.push ARGV[1]; puts "push completed"
30
- when 'follow'; Thimbl::Command.follow( ARGV[1], ARGV[2] ); puts "follow completed"
26
+ when 'post'; Thimbl::Command.post( ARGV[1], ARGV[2] ); puts "post completed"
27
+ when 'follow'; Thimbl::Command.follow( ARGV[1], ARGV[2], ARGV[3] ); puts "follow completed"
31
28
  else
32
29
  puts "command not valid '#{ARGV[0]}'"
33
30
  exit 1
@@ -3,6 +3,7 @@ require 'json'
3
3
  require 'net/scp'
4
4
  require 'fileutils'
5
5
  require 'tempfile'
6
+ require 'ostruct'
6
7
  require "#{File.dirname(__FILE__)}/thimbl/base"
7
8
  require "#{File.dirname(__FILE__)}/thimbl/command"
8
9
  require "#{File.dirname(__FILE__)}/thimbl/finger"
@@ -1,78 +1,67 @@
1
1
  # Thimbl ruby client
2
2
  #
3
3
  # Author: fernandoguillen.info
4
- #
5
4
  # Code: https://github.com/fguillen/ThimblClient
6
- #
7
5
  # Use:
8
6
  # require 'rubygems'
9
7
  # require 'thimbl'
10
8
  # thimbl =
11
9
  # Thimbl::Base.new(
12
- # 'bio' => 'my bio',
13
- # 'website' => 'my website',
14
- # 'mobile' => 'my mobile',
15
- # 'email' => 'my email',
16
- # 'address' => 'username@thimbl.net',
17
- # 'name' => 'my name'
10
+ # 'user@thimbl.net',
11
+ # {
12
+ # :bio => 'my bio',
13
+ # :website => 'my website',
14
+ # :mobile => 'my mobile',
15
+ # :email => 'my email',
16
+ # :name => 'my name'
17
+ # }
18
18
  # )
19
19
  # thimbl.follow 'dk', 'dk@telekommunisten.org'
20
20
  # thimbl.fetch
21
- # thimbl.print
21
+ # thimbl.messages
22
22
  # thimbl.post 'My first post'
23
- # thimbl.push <password>
23
+ # thimbl.push 'password'
24
24
  #
25
25
  module Thimbl
26
+ class NoPlanException < Exception; end
26
27
  class Base
27
- attr_accessor :data
28
+ attr_accessor :data, :address
28
29
 
29
- # Initialize a new configuration, the execution of this method
30
- # will delete any thing in the `thimbl.plan_path` file and `thimbl.cache_path` file.
30
+ # Initialize a new configuration
31
31
  #
32
32
  # Use:
33
33
  # thimbl =
34
34
  # Thimbl::Base.new(
35
- # :bio => 'bio',
36
- # :website => 'website',
37
- # :mobile => 'mobile',
38
- # :email => 'email',
39
- # :address => 'address',
40
- # :name => 'name'
41
- # )
35
+ # 'user@thimbl.net',
36
+ # {
37
+ # :bio => 'bio',
38
+ # :website => 'website',
39
+ # :mobile => 'mobile',
40
+ # :email => 'email',
41
+ # :name => 'name'
42
+ # }
43
+ # )
42
44
  #
43
45
  # or just:
44
46
  #
45
- # thimbl = Thimbl::Base.new
46
- def initialize( opts = {} )
47
- opts = {
48
- 'bio' => 'bio',
49
- 'website' => 'website',
50
- 'mobile' => 'mobile',
51
- 'email' => 'email',
52
- 'address' => 'address',
53
- 'name' => 'name'
54
- }.merge( opts )
55
-
47
+ # thimbl = Thimbl::Base.new( 'user@thimbl.net' )
48
+ def initialize( address, opts = {} )
49
+ @address = address
56
50
  @data = {
57
- 'me' => opts['address'],
58
- 'plans' => {
59
- opts['address'] => {
60
- 'name' => opts['name'],
61
- 'bio' => opts['bio'],
62
- 'properties' => {
63
- 'email' => opts['email'],
64
- 'mobile' => opts['mobile'],
65
- 'website' => opts['website']
66
- },
67
- 'following' => [],
68
- 'messages' => [],
69
- 'replies' => {}
70
- }
71
- }
51
+ 'name' => opts[:name],
52
+ 'bio' => opts[:bio],
53
+ 'properties' => {
54
+ 'email' => opts[:email],
55
+ 'mobile' => opts[:mobile],
56
+ 'website' => opts[:website]
57
+ },
58
+ 'following' => [],
59
+ 'messages' => [],
60
+ 'replies' => {}
72
61
  }
73
62
  end
74
63
 
75
- # Post a new message in your time-line.
64
+ # Post a new message in user's time-line.
76
65
  # _This method doesn't push the modifications to de server._
77
66
  def post( text )
78
67
  message = {
@@ -80,99 +69,125 @@ module Thimbl
80
69
  'text' => text
81
70
  }
82
71
 
83
- data['plans'][me]['messages'] << message
72
+ data['messages'] << message
84
73
  end
85
74
 
86
- # Post a new message in your time-line and push the modifications to the server.
75
+ # Post a new message in user's time-line and push the modifications to the server.
87
76
  def post!( text, password )
88
77
  post text
89
78
  push password
90
79
  end
91
80
 
92
- # Add a new user to your following
81
+ # Add a new user to user's following
93
82
  # _This method doesn't push the modifications to de server._
94
83
  def follow( follow_nick, follow_address )
95
- return if data['plans'][me]['following'].count { |e| e['address'] == follow_address } != 0
96
- data['plans'][me]['following'] << { 'nick' => follow_nick, 'address' => follow_address }
84
+ return if following.count { |e| e.address == follow_address } != 0
85
+ data['following'] << { 'nick' => follow_nick, 'address' => follow_address }
97
86
  end
98
87
 
99
- # Add a new user to your following and push the modifications to the server.
88
+ # Add a new user to user's following and push the modifications to the server.
100
89
  def follow!( follow_nick, follow_address, password )
101
90
  follow follow_nick, follow_address
102
91
  push password
103
92
  end
104
93
 
105
- # Fetch all the info and timelines of all the users you are following.
106
- # Even you, so any not pushed modification will be overwritten
94
+ # Remove a user from the user's following
95
+ # _This method doesn't push the modifications to de server._
96
+ def unfollow( follow_address )
97
+ data['following'].delete_if { |e| e['address'] == follow_address }
98
+ end
99
+
100
+ # Remove a new from user's following and push the modifications to the server.
101
+ def unfollow!( follow_address, password )
102
+ unfollow follow_address
103
+ push password
104
+ end
105
+
106
+ # Updating cached .plan
107
+ # Any not pushed modification will be deleted
107
108
  def fetch
108
- following_and_me = following.map { |f| f['address'] } << me
109
- following_and_me.uniq.each do |address|
110
- address_finger = Thimbl::Finger.run address
111
- next if address_finger.nil? || address_finger.match(/Plan:\s*(.*)/m).nil?
112
- address_plan = address_finger.match(/Plan:\s*(.*)/m)[1].gsub("\\\n",'')
113
- data['plans'][address] = JSON.load( address_plan )
114
- end
109
+ @data = JSON.load( fetch_plan )
115
110
  end
116
111
 
117
- # Send your actual `plan` file to your server
118
- # It requires the password of your thimbl user
112
+ # Send user's cached .plan to user's server
113
+ # It requires the password of user's thimbl user
119
114
  def push( password )
120
- tmp_path = Thimbl::Utils.to_file plan.to_json
121
- Net::SCP.start( me.split('@')[1], me.split('@')[0], :password => password ) do |scp|
115
+ tmp_path = Thimbl::Utils.to_file( data.to_json )
116
+ Net::SCP.start( address.split('@')[1], address.split('@')[0], :password => password ) do |scp|
122
117
  scp.upload!( tmp_path, ".plan" )
123
118
  end
124
119
  end
125
120
 
126
- # Print every message of you and all the users you are following.
127
- #
128
- # The method doesn't print anything by it self. It just returns an string
129
- # with all the comments.
130
- def print
131
- result = ""
132
- messages.each do |message|
133
- result += message['time'].strftime( '%Y-%m-%d %H:%M:%S' )
134
- result += " #{message['address']}"
135
- result += " > #{message['text']}"
136
- result += "\n"
121
+ # Returns all this user's messages
122
+ # in a chronologic order.
123
+ def messages
124
+ return [] if data['messages'].nil?
125
+
126
+ result = []
127
+
128
+ data['messages'].each do |message|
129
+ result << OpenStruct.new({
130
+ :address => address,
131
+ :time => Thimbl::Utils.parse_time( message['time'] ),
132
+ :text => message['text']
133
+ })
137
134
  end
135
+
136
+ result = result.sort { |a,b| a.time <=> b.time }
138
137
 
139
138
  return result
140
139
  end
141
-
142
- # Returns all the messages of you and all the users you are following
143
- # in a chronologic order into a json format.
144
- def messages
140
+
141
+ # Returns all the info about the users this user is following.
142
+ def following
143
+ return [] if data['following'].nil?
144
+
145
145
  result = []
146
146
 
147
- data['plans'].each_pair do |address, plan|
148
- next if plan['messages'].nil?
149
- plan['messages'].each do |message|
150
- result << {
151
- 'address' => address,
152
- 'time' => Thimbl::Utils.parse_time( message['time'] ),
153
- 'text' => message['text']
154
- }
155
- end
147
+ data['following'].each do |chased|
148
+ result << OpenStruct.new({
149
+ :nick => chased['nick'],
150
+ :address => chased['address']
151
+ })
156
152
  end
157
153
 
158
- result = result.sort { |a,b| a['time'] <=> b['time'] }
159
-
160
154
  return result
161
155
  end
162
-
163
- # Returns the actual thimbl user account
164
- def me
165
- data['me']
166
- end
167
156
 
168
- # Returns all the info about the users you are following.
169
- def following
170
- data['plans'][me]['following']
157
+ # Returns all the user properties
158
+ def properties
159
+ OpenStruct.new({
160
+ :bio => data['bio'],
161
+ :name => data['name'],
162
+ :email => data['properties']['email'],
163
+ :mobile => data['properties']['mobile'],
164
+ :website => data['properties']['website']
165
+ })
171
166
  end
172
167
 
173
- # Returns the actual plan
174
- def plan
175
- data['plans'][me]
168
+ # Update all the user properties
169
+ # _This method doesn't push the modifications to de server._
170
+ def properties=( opts = {} )
171
+ data['bio'] = opts[:bio] unless opts[:bio].nil?
172
+ data['name'] = opts[:name] unless opts[:name].nil?
173
+ data['properties']['email'] = opts[:email] unless opts[:email].nil?
174
+ data['properties']['mobile'] = opts[:mobile] unless opts[:mobile].nil?
175
+ data['properties']['website'] = opts[:website] unless opts[:website].nil?
176
176
  end
177
+
178
+ private
179
+
180
+ def fetch_plan
181
+ finger_response = Thimbl::Finger.run address
182
+
183
+ if( finger_response.nil? || finger_response.match(/Plan:\s*(.*)/m).nil? )
184
+ raise NoPlanException, "Not Thimbl Plan in this address: '#{address}'"
185
+ end
186
+
187
+ finger_plan = finger_response.match(/Plan:\s*(.*)/m)[1].gsub("\\\n",'')
188
+
189
+ return finger_plan
190
+ end
191
+
177
192
  end
178
193
  end
@@ -1,76 +1,91 @@
1
1
  module Thimbl
2
2
  class Command
3
- CACHE_PATH = File.expand_path( "~#{ENV['USER']}/.thimbl/cache.json" )
3
+ THIMBL_FOLDER = File.expand_path( "~#{ENV['USER']}/.thimbl" )
4
4
 
5
- def self.setup( *args )
6
- if( args.size != 1 )
7
- raise ArgumentError, "use: $ thimbl setup <thimbl_user>"
8
- end
9
-
10
- thimbl = Thimbl::Base.new( 'address' => args[0] )
11
- save_cache thimbl.data
5
+ def self.version
6
+ "0.2.0"
12
7
  end
13
8
 
14
- def self.save_cache data
15
- if !File.exists? File.dirname( cache_path )
16
- FileUtils.mkdir_p File.dirname( cache_path )
9
+ def self.setup( *args )
10
+ if( args.size != 1 )
11
+ raise ArgumentError, "use: $ thimblr setup <thimbl_user>"
17
12
  end
18
-
19
- File.open( cache_path, 'w' ) do |f|
20
- f.write( data.to_json )
13
+
14
+ if !File.exists? File.dirname( thimbl_folder )
15
+ FileUtils.mkdir_p File.dirname( thimbl_folder )
21
16
  end
17
+
18
+ save_actual args[0]
22
19
  end
23
20
 
24
21
  def self.print
25
- thimbl = Thimbl::Command.load
26
- return thimbl.print
27
- end
28
-
29
- def self.fetch
30
- thimbl = Thimbl::Command.load
22
+ thimbl = get_actual
31
23
  thimbl.fetch
32
- save_cache thimbl.data
33
- end
34
-
35
- def self.post( text )
36
- if( text.nil? || text.empty? )
37
- raise ArgumentError, "use: $ thimbl post <message>"
24
+
25
+ # user's messages
26
+ messages = thimbl.messages
27
+
28
+ # user's following's messages
29
+ thimbl.following.each do |followed|
30
+ thimbl = Thimbl::Base.new followed.address
31
+ begin
32
+ thimbl.fetch
33
+ messages += thimbl.messages
34
+ rescue Thimbl::NoPlanException
35
+ puts "Error fetching #{followed.address} messages"
36
+ end
38
37
  end
39
- thimbl = Thimbl::Command.load
40
- thimbl.post text
41
- save_cache thimbl.data
42
- end
43
-
44
- def self.push( password )
45
- if( password.nil? || password.empty? )
46
- raise ArgumentError, "use: $ thimbl push <password>"
38
+
39
+ messages = messages.sort { |a,b| a.time <=> b.time }
40
+
41
+ result = ""
42
+ messages.each do |message|
43
+ result += message.time.strftime( '%Y-%m-%d %H:%M:%S' )
44
+ result += " #{message.address}"
45
+ result += " > #{message.text}"
46
+ result += "\n"
47
47
  end
48
- thimbl = Thimbl::Command.load
49
- thimbl.push password
48
+
49
+ return result
50
50
  end
51
51
 
52
- def self.follow( nick, address )
53
- if( nick.nil? || nick.empty? || address.nil? || address.empty? )
54
- raise ArgumentError, "use: $ thimbl follow <nick> <address>"
52
+ def self.post( text = nil, password = nil)
53
+ if( text.nil? || text.empty? || password.nil? )
54
+ raise ArgumentError, "use: $ thimblr post <message> <password>"
55
55
  end
56
- thimbl = Thimbl::Command.load
57
- thimbl.follow nick, address
58
- save_cache thimbl.data
56
+
57
+ thimbl = get_actual
58
+ thimbl.post! text, password
59
59
  end
60
60
 
61
- def self.load
62
- if( !File.exists? cache_path )
63
- raise ArgumentError, "Thimbl need to setup, use: $ thimbl setup <thimbl_user>"
61
+ def self.follow( nick = nil, address = nil, password = nil )
62
+ if( nick.nil? || nick.empty? || address.nil? || address.empty? || password.nil? )
63
+ raise ArgumentError, "use: $ thimblr follow <nick> <address> <password>"
64
64
  end
65
-
66
- thimbl = Thimbl::Base.new
67
- thimbl.data = JSON.load File.read cache_path
68
-
69
- return thimbl
65
+
66
+ thimbl = get_actual
67
+ thimbl.follow! nick, address, password
70
68
  end
71
69
 
72
- def self.cache_path
73
- CACHE_PATH
74
- end
70
+ private
71
+
72
+ def self.save_actual( address )
73
+ File.open( "#{thimbl_folder}/actual", 'w' ) { |f| f.write address }
74
+ end
75
+
76
+ def self.get_actual
77
+ if( !File.exists? "#{thimbl_folder}/actual" )
78
+ raise ArgumentError, "Thimbl need to setup, use: $ thimblr setup <thimbl_user>"
79
+ end
80
+
81
+ thimbl = Thimbl::Base.new File.read( "#{thimbl_folder}/actual" )
82
+ thimbl.fetch
83
+
84
+ return thimbl
85
+ end
86
+
87
+ def self.thimbl_folder
88
+ THIMBL_FOLDER
89
+ end
75
90
  end
76
91
  end