thimbl 0.1.2 → 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.
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