glima 0.2.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.
@@ -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