rnote 0.0.1
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/bin/rnote +60 -0
- data/lib/rnote.rb +75 -0
- data/lib/rnote/auth.rb +130 -0
- data/lib/rnote/cmd/create.rb +32 -0
- data/lib/rnote/cmd/edit.rb +36 -0
- data/lib/rnote/cmd/find.rb +24 -0
- data/lib/rnote/cmd/login.rb +68 -0
- data/lib/rnote/cmd/logout.rb +24 -0
- data/lib/rnote/cmd/remove.rb +38 -0
- data/lib/rnote/cmd/show.rb +37 -0
- data/lib/rnote/cmd/who.rb +16 -0
- data/lib/rnote/converter.rb +127 -0
- data/lib/rnote/edit.rb +250 -0
- data/lib/rnote/find.rb +132 -0
- data/lib/rnote/persister.rb +249 -0
- data/lib/rnote/version.rb +3 -0
- data/rnote.rdoc +5 -0
- metadata +232 -0
data/bin/rnote
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'gli'
|
3
|
+
require 'rnote'
|
4
|
+
|
5
|
+
include GLI::App
|
6
|
+
|
7
|
+
program_desc 'Evernote Command Line'
|
8
|
+
|
9
|
+
# version Rnote::VERSION
|
10
|
+
|
11
|
+
desc 'prompt for input and run an editor when necessary'
|
12
|
+
default_value true
|
13
|
+
switch :interactive
|
14
|
+
|
15
|
+
pre do |global,command,options,args|
|
16
|
+
# Pre logic here
|
17
|
+
# Return true to proceed; false to abort and not call the
|
18
|
+
# chosen command
|
19
|
+
# Use skips_pre before a command to skip this block
|
20
|
+
# on that command only
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
post do |global,command,options,args|
|
25
|
+
# Post logic here
|
26
|
+
# Use skips_post before a command to skip this
|
27
|
+
# block on that command only
|
28
|
+
end
|
29
|
+
|
30
|
+
def error_code_to_name(error_code)
|
31
|
+
Evernote::EDAM::Error::EDAMErrorCode.constants.select { |constant| Evernote::EDAM::Error::EDAMErrorCode.const_get(constant) == error_code }.first.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
on_error do |exception|
|
35
|
+
# Error logic here
|
36
|
+
# return false to skip default error handling
|
37
|
+
|
38
|
+
if exception.instance_of?(Evernote::EDAM::Error::EDAMUserException)
|
39
|
+
if exception.errorCode == Evernote::EDAM::Error::EDAMErrorCode::AUTH_EXPIRED
|
40
|
+
puts "Authorization issue. perhaps your password is incorrect or expired."
|
41
|
+
false
|
42
|
+
else
|
43
|
+
puts exception.error_message
|
44
|
+
true
|
45
|
+
end
|
46
|
+
elsif exception.instance_of?(Evernote::EDAM::Error::EDAMSystemException)
|
47
|
+
puts exception.error_message
|
48
|
+
true
|
49
|
+
elsif exception.class == Evernote::EDAM::Error::InvalidXmlError
|
50
|
+
puts exception.xml
|
51
|
+
true
|
52
|
+
else
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
$app = Rnote::App.new
|
59
|
+
|
60
|
+
exit run(ARGV)
|
data/lib/rnote.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
# environment detection
|
4
|
+
begin
|
5
|
+
# this file only exists in development
|
6
|
+
# its not included in the gem,
|
7
|
+
# and thus not found in production (the installed gem)
|
8
|
+
require_relative 'rnote/environment'
|
9
|
+
rescue LoadError
|
10
|
+
# production environment
|
11
|
+
# should only happen in the installed gem.
|
12
|
+
RNOTE_HOME ||= ENV['HOME'] + '/.rnote'
|
13
|
+
RNOTE_TESTING_OK = false
|
14
|
+
RNOTE_SANDBOX_ONLY = false
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'rnote/version'
|
18
|
+
require 'rnote/converter'
|
19
|
+
require 'rnote/persister'
|
20
|
+
require 'rnote/auth'
|
21
|
+
|
22
|
+
# verbs
|
23
|
+
Dir[File.absolute_path(File.dirname(__FILE__)) + '/rnote/cmd/*.rb'].each do |file|
|
24
|
+
require file
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module Rnote
|
29
|
+
|
30
|
+
class App
|
31
|
+
|
32
|
+
attr_reader :persister,:auth
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@persister = Persister.new
|
36
|
+
@auth = Auth.new(@persister)
|
37
|
+
end
|
38
|
+
|
39
|
+
def client
|
40
|
+
auth.client
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
module EDAMErrors
|
48
|
+
|
49
|
+
def error_code_string
|
50
|
+
Evernote::EDAM::Error::EDAMErrorCode.constants.select { |constant_sym|
|
51
|
+
Evernote::EDAM::Error::EDAMErrorCode.const_get(constant_sym) == self.errorCode
|
52
|
+
}.first.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
class Evernote::EDAM::Error::EDAMSystemException
|
58
|
+
|
59
|
+
include EDAMErrors
|
60
|
+
|
61
|
+
def error_message
|
62
|
+
"#{self.error_code_string}(#{self.errorCode}: #{self.message})"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
class Evernote::EDAM::Error::EDAMUserException
|
68
|
+
|
69
|
+
include EDAMErrors
|
70
|
+
|
71
|
+
def error_message
|
72
|
+
"#{self.error_code_string}(#{self.errorCode}): #{self.parameter}"
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/lib/rnote/auth.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
|
2
|
+
require 'mechanize'
|
3
|
+
require 'evernote_oauth'
|
4
|
+
require 'rnote/persister'
|
5
|
+
|
6
|
+
DUMMY_CALLBACK_URL = 'http://www.evernote.com'
|
7
|
+
|
8
|
+
module Rnote
|
9
|
+
|
10
|
+
class Auth
|
11
|
+
|
12
|
+
def initialize(persister=Persister.new)
|
13
|
+
@persister = persister
|
14
|
+
end
|
15
|
+
|
16
|
+
def login_with_developer_token(developer_token,sandbox)
|
17
|
+
if is_logged_in
|
18
|
+
if @persister.get_developer_token == developer_token
|
19
|
+
return
|
20
|
+
else
|
21
|
+
logout
|
22
|
+
end
|
23
|
+
end
|
24
|
+
@persister.persist_developer_token(developer_token)
|
25
|
+
@persister.persist_sandbox(sandbox)
|
26
|
+
end
|
27
|
+
|
28
|
+
def login_with_password(username,password,sandbox)
|
29
|
+
|
30
|
+
if is_logged_in
|
31
|
+
if who == username
|
32
|
+
# already logged in (we don't check against service though)
|
33
|
+
# if a re-login is truely required, the user can just logout first.
|
34
|
+
return
|
35
|
+
else
|
36
|
+
logout
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
## Get a user key using these crednetials
|
41
|
+
|
42
|
+
# this client isn't authorized, and can only request authorization. no api calls.
|
43
|
+
auth_client = EvernoteOAuth::Client.new(
|
44
|
+
consumer_key: @persister.get_consumer_key,
|
45
|
+
consumer_secret: @persister.get_consumer_secret,
|
46
|
+
sandbox: sandbox
|
47
|
+
)
|
48
|
+
|
49
|
+
request_token = auth_client.request_token(:oauth_callback => DUMMY_CALLBACK_URL)
|
50
|
+
oauth_verifier = mechanize_login(request_token.authorize_url, username, password)
|
51
|
+
access_token = request_token.get_access_token(oauth_verifier: oauth_verifier)
|
52
|
+
user_token = access_token.token
|
53
|
+
|
54
|
+
@persister.persist_username(username)
|
55
|
+
@persister.persist_user_token(user_token)
|
56
|
+
@persister.persist_sandbox(sandbox)
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def client
|
61
|
+
# not the same as the client used to get the token.
|
62
|
+
# this one is fully authorized and can make actual api calls.
|
63
|
+
|
64
|
+
if not is_logged_in
|
65
|
+
raise "not logged in"
|
66
|
+
end
|
67
|
+
|
68
|
+
token = @persister.get_user_token || @persister.get_developer_token
|
69
|
+
|
70
|
+
@client ||= EvernoteOAuth::Client.new(token: token, sandbox: @persister.get_sandbox)
|
71
|
+
|
72
|
+
@client
|
73
|
+
end
|
74
|
+
|
75
|
+
def note_store
|
76
|
+
client.note_store
|
77
|
+
end
|
78
|
+
|
79
|
+
def mechanize_login(url, username, password)
|
80
|
+
|
81
|
+
agent = Mechanize.new
|
82
|
+
login_page = agent.get(url)
|
83
|
+
login_form = login_page.form('login_form')
|
84
|
+
raise unless login_form
|
85
|
+
login_form.username = username
|
86
|
+
login_form.password = password
|
87
|
+
accept_page = agent.submit(login_form,login_form.buttons.first)
|
88
|
+
|
89
|
+
if accept_page.form('login_form')
|
90
|
+
# sent us back to the login page
|
91
|
+
raise "bad username/password"
|
92
|
+
elsif not accept_page.form('oauth_authorize_form')
|
93
|
+
raise "failed to login"
|
94
|
+
end
|
95
|
+
|
96
|
+
accept_form = accept_page.form('oauth_authorize_form')
|
97
|
+
# we don't need to go so far as to retrieve the callback url.
|
98
|
+
agent.redirect_ok = false
|
99
|
+
callback_redirect = agent.submit(accept_form, accept_form.buttons.first)
|
100
|
+
response_url = callback_redirect.response['location']
|
101
|
+
oauth_verifier = CGI.parse(URI.parse(response_url).query)['oauth_verifier'][0]
|
102
|
+
|
103
|
+
oauth_verifier
|
104
|
+
end
|
105
|
+
|
106
|
+
def is_logged_in
|
107
|
+
@persister.get_user_token or @persister.get_developer_token
|
108
|
+
end
|
109
|
+
|
110
|
+
def who
|
111
|
+
if is_logged_in
|
112
|
+
@persister.get_username or @persister.get_developer_token
|
113
|
+
else
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def logout
|
120
|
+
# unfortunately, no way to revoke a token via API
|
121
|
+
# TODO perhaps I can redo the oauth, and choose revoke instead of re-accept
|
122
|
+
@persister.forget_user_token
|
123
|
+
@persister.forget_username
|
124
|
+
@persister.forget_developer_token
|
125
|
+
@persister.forget_sandbox
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
require 'gli'
|
3
|
+
|
4
|
+
require 'rnote/edit'
|
5
|
+
|
6
|
+
include GLI::App
|
7
|
+
|
8
|
+
|
9
|
+
desc 'create a note and launch the editor for it'
|
10
|
+
command :create do |verb|
|
11
|
+
verb.command :note do |noun|
|
12
|
+
|
13
|
+
|
14
|
+
Rnote::Edit.include_set_options(noun)
|
15
|
+
Rnote::Edit.include_editor_options(noun)
|
16
|
+
|
17
|
+
noun.action do |global_options,options,args|
|
18
|
+
|
19
|
+
if args.length > 0
|
20
|
+
raise "create doesn't take a search query"
|
21
|
+
end
|
22
|
+
|
23
|
+
edit = Rnote::Edit.new($app.auth)
|
24
|
+
edit.options(options.merge(global_options))
|
25
|
+
edit.edit_action
|
26
|
+
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
verb.default_command :note
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
require 'rnote/edit'
|
3
|
+
require 'rnote/find'
|
4
|
+
|
5
|
+
include GLI::App
|
6
|
+
|
7
|
+
|
8
|
+
desc 'Describe edit here'
|
9
|
+
arg_name 'Describe arguments to edit here'
|
10
|
+
command :edit do |verb|
|
11
|
+
|
12
|
+
verb.command :note do |noun|
|
13
|
+
|
14
|
+
Rnote::Edit.include_set_options(noun)
|
15
|
+
Rnote::Edit.include_editor_options(noun)
|
16
|
+
Rnote::Find.include_search_options(noun)
|
17
|
+
|
18
|
+
noun.action do |global_options,options,args|
|
19
|
+
|
20
|
+
find = Rnote::Find.new($app.auth,$app.persister)
|
21
|
+
note = find.find_note(options.merge(global_options),args)
|
22
|
+
|
23
|
+
edit = Rnote::Edit.new($app.auth)
|
24
|
+
edit.options(options.merge(global_options))
|
25
|
+
edit.note(note)
|
26
|
+
edit.edit_action
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
verb.default_command :note
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
require 'rnote/find'
|
3
|
+
|
4
|
+
include GLI::App
|
5
|
+
|
6
|
+
|
7
|
+
desc 'search for notes/tags/notebooks'
|
8
|
+
command :find do |verb|
|
9
|
+
|
10
|
+
verb.command :note do |noun|
|
11
|
+
|
12
|
+
Rnote::Find.include_search_options(noun)
|
13
|
+
|
14
|
+
noun.action do |global_options,options,args|
|
15
|
+
|
16
|
+
find = Rnote::Find.new($app.auth,$app.persister)
|
17
|
+
find.find_cmd(options.merge(global_options),args)
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
verb.default_command :note
|
23
|
+
end
|
24
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
require 'highline/import'
|
3
|
+
|
4
|
+
include GLI::App
|
5
|
+
|
6
|
+
desc 'provide rnote credentials'
|
7
|
+
command :login do |c|
|
8
|
+
|
9
|
+
c.desc "username"
|
10
|
+
c.flag [:u,:user,:username]
|
11
|
+
|
12
|
+
c.desc "password (if not provided, will ask)"
|
13
|
+
c.flag [:p,:pass,:password]
|
14
|
+
|
15
|
+
c.desc "developer token, if you wish to forgoe a password."
|
16
|
+
c.flag [:d,:'dev-token',:'developer-token']
|
17
|
+
|
18
|
+
c.desc "use the sandbox environment in lue of the production evernote system."
|
19
|
+
c.default_value false
|
20
|
+
c.switch [:s,:sandbox]
|
21
|
+
|
22
|
+
c.desc "provide a consumer key, instead of the included one."
|
23
|
+
c.flag [:k,:key,:'consumer-key']
|
24
|
+
|
25
|
+
c.desc "provide a consumer secret to go along with the consumer key."
|
26
|
+
c.flag [:c,:secret,:'consumer-secret']
|
27
|
+
|
28
|
+
c.action do |global_options,options,args|
|
29
|
+
raise "This command takes no arguments, only options (i.e. --username" unless args.length == 0
|
30
|
+
|
31
|
+
if options[:key]
|
32
|
+
$app.persister.persist_consumer_key(options[:key])
|
33
|
+
end
|
34
|
+
|
35
|
+
if options[:secret]
|
36
|
+
$app.persister.persist_consumer_secret(options[:secret])
|
37
|
+
end
|
38
|
+
|
39
|
+
if options[:d]
|
40
|
+
# first check for a dev token
|
41
|
+
$app.auth.login_with_developer_token(options[:d],options[:sandbox])
|
42
|
+
puts "login successful using developer key"
|
43
|
+
else
|
44
|
+
# then fall back to using a username and password
|
45
|
+
|
46
|
+
if not options[:u]
|
47
|
+
answer = ask("Enter your username: ")
|
48
|
+
options[:u] = answer
|
49
|
+
end
|
50
|
+
|
51
|
+
if not options[:p]
|
52
|
+
answer = ask("Enter your password: ") { |q| q.echo = 'x' }
|
53
|
+
options[:p] = answer
|
54
|
+
end
|
55
|
+
|
56
|
+
$app.auth.login_with_password(options[:user],options[:password], options[:sandbox])
|
57
|
+
|
58
|
+
# test the login with a harmless api call.
|
59
|
+
$app.auth.client.user_store.getUser
|
60
|
+
|
61
|
+
puts "you are now logged in as '#{$app.auth.who}'"
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
include GLI::App
|
3
|
+
|
4
|
+
=begin
|
5
|
+
|
6
|
+
only drops the token.
|
7
|
+
there is no way to ask evernote to revoke the token, from an api.
|
8
|
+
|
9
|
+
will forget about the user,password or developer key.
|
10
|
+
but won't forget about a consumer key, as that by itself is not considered a login.
|
11
|
+
|
12
|
+
=end
|
13
|
+
|
14
|
+
desc 'log user out of rnote'
|
15
|
+
command :logout do |c|
|
16
|
+
c.action do |global_options,options,args|
|
17
|
+
raise unless args.length == 0
|
18
|
+
|
19
|
+
$app.auth.logout
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|