glima 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ git = File.expand_path('../.git', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'glima/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "glima"
9
+ spec.version = Glima::VERSION
10
+ spec.authors = ["Yoshinari Nomura"]
11
+ spec.email = ["nom@quickhack.net"]
12
+ spec.summary = %q{Gmail CLI client}
13
+ spec.description = %q{Gmail CLI client}
14
+ spec.homepage = "https://github.com/yoshinari-nomura/glima"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = if Dir.exist?(git)
18
+ `git ls-files -z`.split("\x0")
19
+ else
20
+ Dir['**/*']
21
+ end
22
+
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.required_ruby_version = ">= 2.3.0"
28
+
29
+ spec.add_runtime_dependency "thor", ">= 0.19.1"
30
+ spec.add_runtime_dependency "google-api-client", ">0.9"
31
+ spec.add_runtime_dependency "googleauth"
32
+ spec.add_runtime_dependency "launchy"
33
+ spec.add_runtime_dependency "mail"
34
+ spec.add_runtime_dependency "rubyzip"
35
+
36
+ spec.add_development_dependency "bundler", "~> 1.11"
37
+ spec.add_development_dependency "rake", "~> 10.0"
38
+ spec.add_development_dependency "rspec", "~> 3.0"
39
+ end
@@ -0,0 +1,18 @@
1
+ module Glima
2
+ # Your code goes here...
3
+ class ConfigurationError < StandardError ; end
4
+
5
+ dir = File.dirname(__FILE__) + "/glima"
6
+
7
+ autoload :Cli, "#{dir}/cli.rb"
8
+ autoload :Command, "#{dir}/command.rb"
9
+ autoload :Config, "#{dir}/config.rb"
10
+ autoload :Context, "#{dir}/context.rb"
11
+ autoload :DataStore, "#{dir}/datastore.rb"
12
+ autoload :GmailClient, "#{dir}/gmail_client.rb"
13
+ autoload :ImapWatch, "#{dir}/imap.rb"
14
+ autoload :QueryParameter, "#{dir}/query_parameter.rb"
15
+ autoload :Resource, "#{dir}/resource.rb"
16
+ autoload :Zip, "#{dir}/zip.rb"
17
+ autoload :VERSION, "#{dir}/version.rb"
18
+ end
@@ -0,0 +1,151 @@
1
+ require "thor"
2
+
3
+ module Glima
4
+ class Cli < Thor
5
+ ################################################################
6
+ # config files
7
+
8
+ CONFIG_HOME = File.join((ENV["XDG_CONFIG_HOME"] || "~/.config"), basename)
9
+ CONFIG_FILE = "config.yml"
10
+ CONFIG_PATH = File.join(CONFIG_HOME, CONFIG_FILE)
11
+
12
+ def self.config_home ; CONFIG_HOME; end
13
+ def self.config_path ; CONFIG_PATH; end
14
+
15
+ ################################################################
16
+ # register preset options
17
+
18
+ def self.named_option(name, options)
19
+ @named_options ||= {}
20
+ @named_options[name] = options
21
+ end
22
+
23
+ def self.expand_option(*names)
24
+ expand_named_option(:method, *names)
25
+ end
26
+
27
+ def self.expand_class_option(*names)
28
+ expand_named_option(:class, *names)
29
+ end
30
+
31
+ def self.expand_named_option(type, *names)
32
+ names.each do |name|
33
+ options = @named_options[name]
34
+ if type == :class
35
+ class_option name, options
36
+ else
37
+ method_option name, options
38
+ end
39
+ end
40
+ end
41
+ private_class_method :expand_named_option
42
+
43
+ named_option :debug, :desc => "Set debug flag", :type => :boolean
44
+ named_option :profile, :desc => "Set profiler flag", :type => :boolean
45
+ named_option :config, :desc => "Set config path (default: #{CONFIG_PATH})", :banner => "FILE"
46
+ named_option :dry_run, :desc => "Perform a trial run with no changes made", :type => :boolean
47
+
48
+ ################################################################
49
+ # command name mappings
50
+
51
+ map ["--version", "-v"] => :version
52
+
53
+ map ["--help", "-h"] => :help
54
+
55
+ default_command :help
56
+
57
+ ################################################################
58
+ # Command: help
59
+ ################################################################
60
+
61
+ desc "help [COMMAND]", "Describe available commands or one specific command"
62
+
63
+ def help(command = nil)
64
+ super(command)
65
+ end
66
+
67
+ ################################################################
68
+ # Command: version
69
+ ################################################################
70
+ desc "version", "Show version"
71
+
72
+ def version
73
+ puts Glima::VERSION
74
+ end
75
+
76
+ ################################################################
77
+ # Command: completions
78
+ ################################################################
79
+ check_unknown_options! :except => :completions
80
+
81
+ desc "completions [COMMAND]", "List available commands or options for COMMAND", :hide => true
82
+
83
+ long_desc <<-LONGDESC
84
+ List available commands or options for COMMAND
85
+ This is supposed to be a zsh compsys helper"
86
+ LONGDESC
87
+
88
+ def completions(*command)
89
+ help = self.class.commands
90
+ global_options = self.class.class_options
91
+ Glima::Command::Completions.new(help, global_options, command, config)
92
+ end
93
+
94
+ ################################################################
95
+ # add some hooks to Thor
96
+
97
+ no_commands do
98
+ def invoke_command(command, *args)
99
+ setup_global_options unless command.name == "init"
100
+ result = super
101
+ teardown
102
+ result
103
+ end
104
+ end
105
+
106
+ ################################################################
107
+ # private
108
+
109
+ private
110
+
111
+ def exit_on_error(&block)
112
+ begin
113
+ yield if block_given?
114
+ rescue Glima::ConfigurationError => e
115
+ STDERR.print "ERROR: #{e.message}.\n"
116
+ exit 1
117
+ end
118
+ end
119
+
120
+ attr_reader :builder, :config, :client, :context, :datastore
121
+
122
+ def setup_global_options
123
+ exit_on_error do
124
+ if options[:profile]
125
+ require 'profiler'
126
+ Profiler__.start_profile
127
+ end
128
+ if options[:debug]
129
+ require "pp"
130
+ $GLIMA_DEBUG = true
131
+ $GLIMA_DEBUG_FOR_DEVELOPER = true if ENV["GLIMA_DEBUG_FOR_DEVELOPER"]
132
+ end
133
+ end
134
+ end
135
+
136
+ def load_plugins
137
+ config_path = options[:config] || CONFIG_PATH
138
+ plugin_dir = File.dirname(config_path)
139
+
140
+ Dir.glob(File.expand_path("plugins/*.rb", plugin_dir)) do |rb|
141
+ require rb
142
+ end
143
+ end
144
+
145
+ def teardown
146
+ if options[:profile]
147
+ Profiler__.print_profile($stdout)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,35 @@
1
+ module Glima
2
+ module Command
3
+
4
+ def self.logger
5
+ @logger
6
+ end
7
+
8
+ def self.client
9
+ @client
10
+ end
11
+
12
+ class << self
13
+ attr_writer :logger, :client
14
+ end
15
+
16
+ dir = File.dirname(__FILE__) + "/command"
17
+
18
+ autoload :Base, "#{dir}/base.rb"
19
+ autoload :Dezip, "#{dir}/dezip.rb"
20
+ # autoload :Events, "#{dir}/events.rb"
21
+ autoload :Guess, "#{dir}/guess.rb"
22
+ autoload :Label, "#{dir}/label.rb"
23
+ autoload :Labels, "#{dir}/labels.rb"
24
+ # autoload :Open, "#{dir}/open.rb"
25
+ autoload :Profile, "#{dir}/profile.rb"
26
+ autoload :Push, "#{dir}/push.rb"
27
+ autoload :Relabel, "#{dir}/relabel.rb"
28
+ autoload :Scan, "#{dir}/scan.rb"
29
+ # autoload :Show, "#{dir}/show.rb"
30
+ autoload :Trash, "#{dir}/trash.rb"
31
+ autoload :Watch, "#{dir}/watch.rb"
32
+ autoload :Xzip, "#{dir}/xzip.rb"
33
+
34
+ end # module Command
35
+ end # module Glima
@@ -0,0 +1,23 @@
1
+ module Glima
2
+ module Command
3
+ class Base
4
+
5
+ private
6
+
7
+ def logger
8
+ Glima::Command.logger
9
+ end
10
+
11
+ def client
12
+ Glima::Command.client
13
+ end
14
+
15
+ def exit_if_error(message, error, logger)
16
+ return true unless error
17
+ logger.error "#{error.message.split(':').last.strip} #{message}."
18
+ exit 1
19
+ end
20
+
21
+ end # class Scan
22
+ end # module Command
23
+ end # module Glima
@@ -0,0 +1,45 @@
1
+ module Glima
2
+ module Command
3
+ class Dezip < Base
4
+
5
+ def initialize(gmail_id, directory, password_file = nil, password_dir = nil)
6
+
7
+ unless File.writable?(File.expand_path(directory))
8
+ logger.error "#{directory} is not writable."
9
+ exit 1
10
+ end
11
+
12
+ mail = client.get_user_smart_message(gmail_id) do |m, err|
13
+ exit_if_error(gmail_id, err, logger)
14
+ end
15
+
16
+ # get password candidates from config file
17
+ password_candidates = []
18
+ if File.exists?(password_file)
19
+ password_candidates += File.open(password_file) {|f| f.read.split(/\n+/) }
20
+ end
21
+
22
+ # gather password candidates from nearby mails
23
+ client.nearby_mails(mail) do |nm|
24
+ logger.info "Passwordish mail: " + nm.format_summary
25
+ password_candidates += nm.find_passwordish_strings
26
+ end
27
+
28
+ # try to unlock zip attachments
29
+ unless mail.unlock_zip!(password_candidates, logger)
30
+ logger.info "Password unlock failed."
31
+ return false
32
+ end
33
+
34
+ # Write to unlocked zip file to DIRECTORY
35
+ mail.attachments.each do |attachment|
36
+ next unless attachment.filename =~ /\.zip$/i
37
+ zip_filename = File.expand_path(attachment.filename, directory)
38
+ Glima::Zip.new(attachment.body.decoded).write_to_file(zip_filename)
39
+ logger.info "Wrote to #{zip_filename || 'STDOUT'}."
40
+ end
41
+ end
42
+
43
+ end # class Dezip
44
+ end # module Command
45
+ end # module Glima
@@ -0,0 +1,30 @@
1
+ module Glima
2
+ module Command
3
+ class Guess < Base
4
+
5
+ def initialize(message_id)
6
+
7
+ fmt = "minimal"
8
+ user_label_ids = []
9
+
10
+ msg = client.get_user_message('me', message_id, format: fmt)
11
+ thr = client.get_user_thread('me', msg.thread_id, format: fmt)
12
+
13
+ thr.messages.each do |tmsg|
14
+ # puts tmsg.snippet
15
+ tmsg.label_ids.each do |label_id|
16
+ next unless label_id =~ /^Label_\d+$/
17
+ user_label_ids << label_id unless user_label_ids.member?(label_id)
18
+ puts "#{tmsg.id} -> #{label_id}"
19
+ end
20
+ end
21
+
22
+ user_label_ids.each do |label_id|
23
+ label = client.get_user_label(label_id)
24
+ puts "#{label_id} -> #{label.name}"
25
+ end
26
+ end
27
+
28
+ end # class Guess
29
+ end # module Command
30
+ end # module Glima
@@ -0,0 +1,29 @@
1
+ module Glima
2
+ module Command
3
+ class Label < Base
4
+
5
+ def initialize(message_id, add, del)
6
+
7
+ req = {}
8
+ req[:add_label_ids] = add.map(&:id) unless add.empty?
9
+ req[:remove_label_ids] = del.map(&:id) unless add.empty?
10
+
11
+ if req.empty?
12
+ puts "Do nothing."
13
+ return 0
14
+ end
15
+
16
+ req = Google::Apis::GmailV1::ModifyMessageRequest.new(req)
17
+
18
+ client.modify_message('me', message_id, req) do |res, err|
19
+ if res
20
+ puts "Update #{message_id} successfully."
21
+ else
22
+ puts "Error: #{err}"
23
+ end
24
+ end
25
+ end
26
+
27
+ end # class Label
28
+ end # module Command
29
+ end # module Glima
@@ -0,0 +1,63 @@
1
+ module Glima
2
+ module Command
3
+ class Labels < Base
4
+
5
+ def initialize(verbose = nil)
6
+
7
+ labels = client.labels
8
+
9
+ if labels.empty?
10
+ puts 'No labels found'
11
+ return 0
12
+ end
13
+
14
+ total = labels.length
15
+
16
+ unless verbose
17
+ labels.sort_by(&:name).each do |label|
18
+ puts "#{label.name}"
19
+ end
20
+ return 0
21
+ end
22
+
23
+ # Gmail API has rate limit at 250 requests/seccond/user (deps on type of method)
24
+ # https://developers.google.com/gmail/api/v1/reference/quota
25
+ # labels.get consumes 1quota unit
26
+ # It is only an experiment, not practical...
27
+ #
28
+ # how to retry batch requests? Issue #444 google/google-api-ruby-client
29
+ # https://github.com/google/google-api-ruby-client/issues/444
30
+ # Setting default option should also work, but it has to be done before the service is created.
31
+ #
32
+ # Retries on individual operations within a batch isn't yet
33
+ # supported. It's a bit complicated to do that correctly
34
+ # (e.g. extract the failed requests/responses, build a new batch,
35
+ # retry, repeat... merge all the results...)
36
+ #
37
+ # I'd caution against using retries with batches unless you know
38
+ # the operations are safe to repeat. Since the entire batch is
39
+ # repeated, you may be replaying successful operations as part of
40
+ # it.
41
+ #
42
+ index = 1
43
+ labels.each_slice(100) do |chunk|
44
+ client.batch do |batch_client|
45
+ chunk.each do |lbl|
46
+ batch_client.get_user_label(lbl.id) do |label, err|
47
+ if label
48
+ puts "--- #{index}/#{total} -------------------------------------------------"
49
+ puts Glima::Resource::Label.new(label).dump
50
+ index += 1
51
+ else
52
+ puts "Error: #{err}"
53
+ end
54
+ end
55
+ end # chunk
56
+ end # batch
57
+ sleep 1
58
+ end # slice
59
+ end
60
+
61
+ end # class Labels
62
+ end # module Command
63
+ end # module Glima
@@ -0,0 +1,15 @@
1
+ module Glima
2
+ module Command
3
+ class Profile < Base
4
+
5
+ def initialize
6
+ response = client.get_user_profile('me')
7
+ puts "emailAddress: #{response.email_address}"
8
+ puts "messagesTotal: #{response.messages_total}"
9
+ puts "threadsTotal: #{response.threads_total}"
10
+ puts "historyId: #{response.history_id}"
11
+ end
12
+
13
+ end # class Profile
14
+ end # module Command
15
+ end # module Glima